将IEntityTypeConfiguration与基本实体一起使用

时间:2017-10-27 15:15:07

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

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

如何扩展此模式以使用基本实体?在下面的示例中,如何在BaseEntityConfigurationLanguageConfiguration中使用MaintainerConfiguration来减少重复,仅在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");
    }
}

5 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:0)

如果不想重复从相同基础实体继承的所有模型的“定义”列,则可以采用另一种方法:

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

}

是查找从基本实体继承的所有实体,对其进行循环,然后调用通用方法,如下所示,其中放置了冗余逻辑:

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

在我看来,它有点笨拙,但对我来说很好!

更多信息的来源:

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

答案 3 :(得分:0)

我参加聚会很晚,但这是我在OnModelCreating方法中实现的效果。

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

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

我直接进行了测试,并通过EF迁移进行了测试,该操作确认生成了正确的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;
    }
}

答案 4 :(得分:0)

我使用以下模式,派生配置只调用 base.Configure。

public abstract class BaseEntity
{
}

public class BaseEntityTypeConfiguration<TEntity>
           : IEntityTypeConfiguration<TEntity> where TEntity : BaseEntity
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        // configure base entity
    }
}

public class DerivedEntity : BaseEntity
{
}

public class EntityTypeConfiguration<TEntity> 
           : BaseEntityTypeConfiguration<TEntity> where TEntity : DerivedEntity
{
    public override void Configure(EntityTypeBuilder<TEntity> builder)
    {
        base.Configure(builder);
        // configure derived entity
    }
}