【问题标题】:Use IEntityTypeConfiguration with a base entity将 IEntityTypeConfiguration 与基本实体一起使用
【发布时间】:2018-04-09 06:05:01
【问题描述】:

在 EF Core 2.0 中,我们能够从 IEntityTypeConfiguration 派生以获得更清晰的 Fluent API 映射 (source)。

如何扩展此模式以利用基本实体?在下面的示例中,我如何使用BaseEntityConfiguration 来减少LanguageConfigurationMaintainerConfiguration 中的重复,仅修改BaseEntity 中的属性BaseEntityConfiguration?这样的BaseEntityConfiguration 会是什么样子?如果有的话,它将如何在OnModelCreating() 中使用?请参阅示例末尾附近的代码中的 TODO。

例子:

public abstract class BaseEntity
{
    public long Id { get; set; }
    public DateTime CreatedDateUtc { get; set; }
    public DateTime? ModifiedDateUtc { get; set; }
}

public class Language : BaseEntity
{
    public string Iso6392 { get; set; }
    public string LocalName { get; set; }
    public string Name { get; set; }
}

public class Maintainer : BaseEntity
{
    public string Email { get; set; }
    public string Name { get; set; }
}

public class FilterListsDbContext : DbContext
{
    public FilterListsDbContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Language> Languages { get; set; }
    public DbSet<Maintainer> Maintainers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //TODO: Possibly add something like BaseEntityConfiguration?
        modelBuilder.ApplyConfiguration(new LanguageConfiguration());
        modelBuilder.ApplyConfiguration(new MaintainerConfiguration());
    }
}

public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
    public void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
    {
        //TODO: Move this to something like BaseEntityConfiguration?
        entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
    }
}

public class MaintainerConfiguration : IEntityTypeConfiguration<Maintainer>
{
    public void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
    {
        //TODO: Move this to something like BaseEntityConfiguration?
        entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
    }
}

【问题讨论】:

    标签: c# asp.net-core entity-framework-core ef-fluent-api ef-core-2.0


    【解决方案1】:

    这样的东西可以工作(未经测试)?

    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);
        }
    }
    

    【讨论】:

    • 谢谢,我会试一试。你会建议我的 OnModelCreating() 使用这个解决方案是什么样的?
    • 应该是一样的。只需确保在每个 Configure() 覆盖中调用 base.Configure。
    • @CalC... "b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP")" 是否与在类构造函数中设置属性值相同?
    • @dinotom - 它有类似的效果,但是应该注意的是,当使用 CURRENT_TIMESTAMP 时,时间将来自 数据库实例所在计算机的操作系统 正在运行,不会考虑数据库时区偏移 - 请参阅 CURRENT_TIMESTAMP
    • 为什么要把BaseEntityTypeConfiguration抽象化?
    【解决方案2】:

    还有一种方法可以解决这个问题,那就是使用模板法设计模式。像这样:

    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");        
        }
    }
    

    通过这种方式,您无需在子配置中编写任何单行代码。

    【讨论】:

    • 如果我们有多重继承,该方法将不起作用:BaseEntity、IdEntity、NamedEntity 等
    • @serge 我猜你的意思是更深的继承树(不是多重继承)。为此,您可以回复该模式并在 MaintainerConfiguration 中调用另一个抽象/虚拟方法。
    • @Wouter 我花了几个小时试图让 2 级继承工作(IdEntity > AuditableEntity > ConcreteEntity),但这似乎不可能。我总是会以我的迁移结束,只从最终基地获取配置,而忽略中间的那些。我认为问题是,你的继承链中只能有一个 IEntityTypeConfiguration (如果你尝试在中间配置中应用它,base.Configure 会抱怨类型不匹配)。
    • @serge 你能弄清楚“多重继承”吗?
    • @Eternal21 因为您覆盖并且不调用中间配置,所以这种模式似乎有点破损,我将发布我最终得到的那个。
    【解决方案3】:

    如果您不想为所有继承自同一基本实体的模型重复列定义,则另一种方法是:

    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 ");
            // …
    
    }
    

    就是找到所有继承自基础Entity的Entites,遍历它们并调用通用方法,如下图,其中放置了冗余的Logic:

    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
    }
    

    对于“GetEntityTypes”方法,您需要 Nuget 包“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;
    }
    

    在我看来它有点 hacky,但对我来说很好用!

    更详细的来源:

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

    【讨论】:

      【解决方案4】:

      我迟到了,但这是我在 OnModelCreating 方法中所做的,以实现类似的结果。

      基本上,我有 (4) 个从 BaseEntity 继承的属性。其中两个是日期,为什么两个是字符串。

      对于日期,我希望默认为 SQL 的 GETUTCDATE,字符串为“SystemGenerated”。使用允许我以强类型方式从 BaseEntity 检索属性名称的静态帮助程序,我获取 (4) 属性名称。然后,在我的主要映射设置完成后,我对所有 ModelBuilder 实体进行迭代。这允许 modelBuilder.Model.GetEntityTypes 返回 modelBuidler 知道的实体。然后就是查看 ClrType.BaseType 以查看该类型是否继承自我的 BaseEntity 并在 PropertyBuilder 上设置默认值。

      我直接通过 EF Migrations 对此进行了测试,确认生成了正确的 SQL。

      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");
          }
      }
      

      这是获取给定类型的属性名称的静态帮助器。

      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;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-09-23
        • 2017-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多