Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for generic ID for EF entity #275

Merged
merged 1 commit into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions MADE.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MADE.Data.Serialization", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MADE.Data.Serialization.Tests", "tests\MADE.Data.Serialization.Tests\MADE.Data.Serialization.Tests.csproj", "{7D789D04-A010-4F11-91AD-B1B94A23BAE0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MADE.Data.EFCore.Tests", "tests\MADE.Data.EFCore.Tests\MADE.Data.EFCore.Tests.csproj", "{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Expand Down Expand Up @@ -1315,6 +1317,62 @@ Global
{7D789D04-A010-4F11-91AD-B1B94A23BAE0}.Release|x64.Build.0 = Release|Any CPU
{7D789D04-A010-4F11-91AD-B1B94A23BAE0}.Release|x86.ActiveCfg = Release|Any CPU
{7D789D04-A010-4F11-91AD-B1B94A23BAE0}.Release|x86.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|ARM64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|ARM64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|ARM.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|ARM64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|ARM64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|iPhone.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|x64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|x64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|x86.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.AppStore|x86.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|ARM.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|ARM64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|iPhone.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|x64.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|x64.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|x86.ActiveCfg = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Debug|x86.Build.0 = Debug|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|Any CPU.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|ARM.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|ARM.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|ARM64.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|ARM64.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|iPhone.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|iPhone.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|x64.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|x64.Build.0 = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|x86.ActiveCfg = Release|Any CPU
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1342,6 +1400,7 @@ Global
{D8C1B3CC-B5BA-4946-944E-D898AB4DFF6E} = {69149D0F-BB09-411B-88F0-A1E845058D70}
{0ACCC377-5FA5-47D9-B6EF-7936F1038B90} = {01380FB8-F8A7-4416-AABA-5407574B7723}
{7D789D04-A010-4F11-91AD-B1B94A23BAE0} = {69149D0F-BB09-411B-88F0-A1E845058D70}
{7ECFE1EE-A42A-495E-BBB1-A34C95F70413} = {69149D0F-BB09-411B-88F0-A1E845058D70}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3921AD86-E6C0-4436-8880-2D9EDFAD6151}
Expand Down
31 changes: 31 additions & 0 deletions src/MADE.Data.EFCore/EntityBase{TKey}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// MADE Apps licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace MADE.Data.EFCore
{
using System;
using System.ComponentModel.DataAnnotations.Schema;

/// <summary>
/// Defines a base definition for an entity.
/// </summary>
/// <typeparam name="TKey">The type of unique identifier for the entity.</typeparam>
public abstract class EntityBase<TKey> : IEntityBase<TKey>
{
/// <summary>
/// Gets or sets the identifier of the entity.
/// </summary>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public TKey Id { get; set; }

/// <summary>
/// Gets or sets the date of the entity's creation.
/// </summary>
public DateTime CreatedDate { get; set; }

/// <summary>
/// Gets or sets the date of the entity's last update.
/// </summary>
public DateTime? UpdatedDate { get; set; }
}
}
4 changes: 2 additions & 2 deletions src/MADE.Data.EFCore/Extensions/DbContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ public static void SetEntityDates(this DbContext context)
IEnumerable<EntityEntry> entries = context.ChangeTracker
.Entries()
.Where(
entry => entry.Entity is IEntityBase &&
entry => entry.Entity is IDatedEntity &&
entry.State is EntityState.Added or EntityState.Modified);

DateTime now = DateTime.UtcNow;

foreach (EntityEntry entry in entries)
{
var entity = (IEntityBase)entry.Entity;
var entity = (IDatedEntity)entry.Entity;
entity.UpdatedDate = now;

if (entry.State == EntityState.Added && entity.CreatedDate == DateTime.MinValue)
Expand Down
17 changes: 16 additions & 1 deletion src/MADE.Data.EFCore/Extensions/EntityBaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace MADE.Data.EFCore.Extensions
{
using System;
using MADE.Data.EFCore.Converters;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

Expand All @@ -19,6 +20,20 @@ public static class EntityBaseExtensions
/// <returns>The entity type builder.</returns>
public static EntityTypeBuilder<TEntity> Configure<TEntity>(this EntityTypeBuilder<TEntity> builder)
where TEntity : class, IEntityBase
{
builder.ConfigureWithKey<TEntity, Guid>();
return builder;
}

/// <summary>
/// Configures the default properties of an <typeparamref name="TEntity">entity</typeparamref>.
/// </summary>
/// <typeparam name="TEntity">The type of entity to configure.</typeparam>
/// <typeparam name="TKey">The type of unique identifier for the entity.</typeparam>
/// <param name="builder">The entity type builder associated with the entity.</param>
/// <returns>The entity type builder.</returns>
public static EntityTypeBuilder<TEntity> ConfigureWithKey<TEntity, TKey>(this EntityTypeBuilder<TEntity> builder)
where TEntity : class, IEntityBase<TKey>
{
builder.HasKey(e => e.Id);
builder.ConfigureDateProperties();
Expand All @@ -33,7 +48,7 @@ public static EntityTypeBuilder<TEntity> Configure<TEntity>(this EntityTypeBuild
/// <returns>The entity type builder.</returns>
public static EntityTypeBuilder<TEntity> ConfigureDateProperties<TEntity>(
this EntityTypeBuilder<TEntity> builder)
where TEntity : class, IEntityBase
where TEntity : class, IDatedEntity
{
builder.Property(x => x.CreatedDate).IsUtc();
builder.Property(x => x.UpdatedDate).IsUtc();
Expand Down
9 changes: 6 additions & 3 deletions src/MADE.Data.EFCore/Extensions/QueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ public static IQueryable<T> Page<T>(this IQueryable<T> query, int page, int page
/// <returns>The ordered query.</returns>
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string sortName, bool sortDesc)
{
return string.IsNullOrWhiteSpace(sortName)
? query
: (!sortDesc ? query.AddOrAppendOrderBy(sortName) : query.AddOrAppendOrderByDescending(sortName));
if (string.IsNullOrWhiteSpace(sortName))
{
return query;
}

return !sortDesc ? query.AddOrAppendOrderBy(sortName) : query.AddOrAppendOrderByDescending(sortName);
}
}
}
23 changes: 23 additions & 0 deletions src/MADE.Data.EFCore/IDatedEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// MADE Apps licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace MADE.Data.EFCore
{
using System;

/// <summary>
/// Defines a base definition for an entity with defined created and updated date.
/// </summary>
public interface IDatedEntity
{
/// <summary>
/// Gets or sets the date of the entity's creation.
/// </summary>
DateTime CreatedDate { get; set; }

/// <summary>
/// Gets or sets the date of the entity's last update.
/// </summary>
DateTime? UpdatedDate { get; set; }
}
}
16 changes: 1 addition & 15 deletions src/MADE.Data.EFCore/IEntityBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,7 @@ namespace MADE.Data.EFCore
/// <summary>
/// Defines a base definition for an entity.
/// </summary>
public interface IEntityBase
public interface IEntityBase : IEntityBase<Guid>
{
/// <summary>
/// Gets or sets the identifier of the entity.
/// </summary>
Guid Id { get; set; }

/// <summary>
/// Gets or sets the date of the entity's creation.
/// </summary>
DateTime CreatedDate { get; set; }

/// <summary>
/// Gets or sets the date of the entity's last update.
/// </summary>
DateTime? UpdatedDate { get; set; }
}
}
17 changes: 17 additions & 0 deletions src/MADE.Data.EFCore/IEntityBase{TKey}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// MADE Apps licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace MADE.Data.EFCore
{
/// <summary>
/// Defines a base definition for an entity with a defined primary key type.
/// </summary>
/// <typeparam name="TKey">The type of unique identifier for the entity.</typeparam>
public interface IEntityBase<TKey> : IDatedEntity
{
/// <summary>
/// Gets or sets the identifier of the entity.
/// </summary>
TKey Id { get; set; }
}
}
72 changes: 72 additions & 0 deletions tests/MADE.Data.EFCore.Tests/Data/TestDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace MADE.Data.EFCore.Tests.Data
{
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Converters;
using Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

[ExcludeFromCodeCoverage]
public class TestDbContext : DbContext
{
public DbSet<TestEntity> Entities { get; set; }

public DbSet<TestKeyedEntity> KeyEntities { get; set; }

public TestDbContext(DbContextOptions<TestDbContext> options) : base(options) { }

public static TestDbContext CreateInMemoryContext(string dbName)
{
var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
DbContextOptions<TestDbContext> options = optionsBuilder.UseInMemoryDatabase(dbName).Options;
return new TestDbContext(options);
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
this.SetEntityDates();
return base.SaveChangesAsync(cancellationToken);
}

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
this.SetEntityDates();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("dbo");
modelBuilder.ApplyConfigurationsFromAssembly(typeof(TestDbContext).Assembly);
modelBuilder.ApplyUtcDateTimeConverter();
}
}

public class TestEntity : EntityBase
{
public string Name { get; set; }
}

public class TestKeyedEntity : EntityBase<int>
{
public string Name { get; set; }
}

public class TestEntityTypeConfiguration : IEntityTypeConfiguration<TestEntity>
{
public void Configure(EntityTypeBuilder<TestEntity> builder)
{
builder.Configure();
}
}

public class TestKeyedEntityTypeConfiguration : IEntityTypeConfiguration<TestKeyedEntity>
{
public void Configure(EntityTypeBuilder<TestKeyedEntity> builder)
{
builder.ConfigureWithKey<TestKeyedEntity, int>();
}
}
}
27 changes: 27 additions & 0 deletions tests/MADE.Data.EFCore.Tests/MADE.Data.EFCore.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="34.0.2" />
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
<PackageReference Include="Shouldly" Version="4.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MADE.Data.EFCore\MADE.Data.EFCore.csproj" />
</ItemGroup>

</Project>
Loading