Use IEntityTypeConfiguration with a base entity

后端 未结 4 989
醉梦人生
醉梦人生 2021-02-05 14:40

In EF Core 2.0, we have the ability to derive from IEntityTypeConfiguration for cleaner Fluent API mappings (source).

How can I extend this pattern to utili

相关标签:
4条回答
  • 2021-02-05 14:40

    There is another way to solve the problem, and that is to use Template Method Design Pattern. Like this:

    public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
        where TBase : BaseEntity
    {
        public void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
        {
            //Base Configuration
    
            ConfigureOtherProperties(builder);
        }
    
        public abstract void ConfigureOtherProperties(EntityTypeBuilder<TEntity> builder);
    }
    
    public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
    {
        public override void ConfigureOtherProperties(EntityTypeBuilder<Maintainer> entityTypeBuilder)
        {
            entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");        
        }
    }
    

    With this way you don't need to write any single line in child configuration.

    0 讨论(0)
  • 2021-02-05 14:41

    Something like this could work (untested)?

    public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
        where TBase : BaseEntity
    {
        public virtual void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
        {
            //Base Configuration
        }
    }
    
    public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
    {
        public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
        {
            entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
            base.Configure(entityTypeBuilder);
        }
    }
    
    0 讨论(0)
  • 2021-02-05 15:03

    Another approach if you dont want to repeat the column Definitions for all of your Models that inherit from the same base Entity like this:

    protected override void OnModelCreating(ModelBuilder modelBuilder){
            modelBuilder.Entity<Order>()
                .Property(b => b.CreatedDateTime)
                .HasDefaultValueSql("CURRENT_TIMESTAMP ");
    
            modelBuilder.Entity<Adress>()
                .Property(b => b.CreatedDateTime)
                .HasDefaultValueSql("CURRENT_TIMESTAMP ");
            // …
    
    }
    

    is to find all the Entites that inhert from the base Entity, loop over them and call the generic Method as shown below, in which the redundant Logic is placed:

    protected override void OnModelCreating(ModelBuilder modelBuilder){
        foreach (Type type in GetEntityTypes(typeof(BaseEntity))){
            var method = SetGlobalQueryMethod.MakeGenericMethod(type);
            method.Invoke(this, new object[] { modelBuilder });
        }
    }
    
    static readonly MethodInfo SetGlobalQueryMethod = typeof(/*your*/Context)
        .GetMethods(BindingFlags.Public | BindingFlags.Instance)
        .Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
    
    public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity{
        builder.Entity<T>().Property(o => o.CreatedDateTime).HasDefaultValueSql("CURRENT_TIMESTAMP");
        // Additional Statements
    }
    

    For the "GetEntityTypes" Method you need the Nuget Package „Microsoft.Extensions.DependencyModel“

    private static IList<Type> _entityTypeCache;
    private static IList<Type> GetEntityTypes(Type type)
    {
        if (_entityTypeCache != null && _entityTypeCache.First().BaseType == type)
        {
            return _entityTypeCache.ToList();
        }
    
        _entityTypeCache = (from a in GetReferencingAssemblies()
                            from t in a.DefinedTypes
                            where t.BaseType == type
                            select t.AsType()).ToList();
    
        return _entityTypeCache;
    }
    
    private static IEnumerable<Assembly> GetReferencingAssemblies()
    {
        var assemblies = new List<Assembly>();
        var dependencies = DependencyContext.Default.RuntimeLibraries;
    
        foreach (var library in dependencies)
        {
            try
            {
                var assembly = Assembly.Load(new AssemblyName(library.Name));
                assemblies.Add(assembly);
            }
            catch (FileNotFoundException)
            { }
        }
        return assemblies;
    }
    

    Its a bit hacky in my opinion, but works fine for me!

    The source with more details:

    https://www.codingame.com/playgrounds/5514/multi-tenant-asp-net-core-4---applying-tenant-rules-to-all-enitites

    0 讨论(0)
  • 2021-02-05 15:06

    I'm late to the party, but this is what I did in the OnModelCreating method to achieve similar results.

    Basically, I have (4) properties that inherit from a BaseEntity. Two of those are dates why two are strings.

    For the dates, I wanted the default to be SQL's GETUTCDATE and the string to be "SystemGenerated." Using a static helper that allows me to retrieve the property name from BaseEntity in a strongly-typed manner, I grab the (4) property names. Then, I iterate over all of the iterate over all of the ModelBuilder entities after my primary mappings are set-up. This allows modelBuilder.Model.GetEntityTypes to return the entities that the modelBuidler is aware of. Then it's a matter of looking at the ClrType.BaseType to see if the type inherits from my BaseEntity and setting the defaults on the PropertyBuilder.

    I tested this directly and through EF Migrations which confirmed that the proper SQL was generated.

    var createdAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedAtUtc);
    var lastModifiedAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedAtUtc);
    var createdBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedBy);
    var lastModifiedBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedBy);
    foreach (var t in modelBuilder.Model.GetEntityTypes())
    {
        if (t.ClrType.BaseType == typeof(BaseEntity))
        {
            modelBuilder.Entity(t.ClrType).Property(createdAtUtc).HasDefaultValueSql("GETUTCDATE()");
            modelBuilder.Entity(t.ClrType).Property(lastModifiedAtUtc).HasDefaultValueSql("GETUTCDATE()");
            modelBuilder.Entity(t.ClrType).Property(createdBy).HasDefaultValueSql("SystemGenerated");
            modelBuilder.Entity(t.ClrType).Property(lastModifiedBy).HasDefaultValueSql("SystemGenerated");
        }
    }
    

    Here is the the static helper for getting property names for a given type..

    public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
    {
        if (expression.Body is MemberExpression)
        {
            return ((MemberExpression)expression.Body).Member.Name;
        }
        else
        {
            var op = ((UnaryExpression)expression.Body).Operand;
            return ((MemberExpression)op).Member.Name;
        }
    }
    
    0 讨论(0)
提交回复
热议问题