获取实体类型:在EF Core中使用.Property<TEntity>的通用版本配置实体属性。

6

在我的EF Core项目中,我有一些从基类DBEntity继承的实体:

public abstract class DBEntity
{
    public int Id { get; set; }

    public DateTime CreatedOn { get; set; }

    public DateTime UpdatedOn { get; set; }

    public EntityStatus EntityStatus { get; set; }
}

我希望为每个继承自DBEntity的实体设置一些具有默认值的特定属性。目前,我正在使用以下代码:
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {

        var entities = modelBuilder.Model
            .GetEntityTypes()
            .Where(w => w.ClrType.IsSubclassOf(typeof(DBEntity)))
            .Select(p => modelBuilder.Entity(p.ClrType));

        foreach (var entity in entities)
        {

            entity
                .Property("CreatedOn")
                .HasDefaultValueSql("GETDATE()");

            entity
                .Property("UpdatedOn")
                .HasComputedColumnSql("GETDATE()");

            entity
                .Property("EntityStatus")
                .HasDefaultValue(EntityStatus.Created);

        }
    }

这个方法可以正常工作,但我不想使用字符串常量来指定属性名称(这样不利于重构等)。

有没有一种方法可以循环遍历从DBEntity继承的实体,并使用属性表达式来配置默认值,就像这样:

foreach (var entity in entities)
{
    entity
      .Property(k => k.CreatedOn)
      .HasDefaultValueSql("GETDATE()");

// ....

}

重要限制:我不能直接调用modelBuilder.Entity().Property(k => k.CreatedOn),因为它会破坏所有的表格。


1
可以实现,但更简单的方法是使用 nameof 而不是字符串常量。 - Ivan Stoev
1
@IvanStoev nameof(DBEntity.CreatedOn) 可以用!还有其他方法吗? - iPath ツ
创建一个通用方法并通过反射调用它。 - Ivan Stoev
2个回答

9

正如评论中所指出的,您可以简单地用 nameof(DBEntity.CreatedOn) 等替换字符串常量。

但是,如果您想使用类型化访问器工作,您可以将基本实体配置代码移动到一个泛型方法中(泛型参数为实际实体类型),并通过反射调用它。

例如,将以下内容添加到您的 DbContext 派生类中:

static void ConfigureDBEntity<TEntity>(ModelBuilder modelBuilder)
    where TEntity : DBEntity
{
    var entity = modelBuilder.Entity<TEntity>();

    entity
        .Property(e => e.CreatedOn)
        .HasDefaultValueSql("GETDATE()");

    entity
        .Property(e => e.UpdatedOn)
        .HasComputedColumnSql("GETDATE()");

    entity
        .Property(e => e.EntityStatus)
        .HasDefaultValue(EntityStatus.Created);

}

然后使用类似以下的代码:
var entityTypes = modelBuilder.Model
        .GetEntityTypes()
        .Where(t => t.ClrType.IsSubclassOf(typeof(DBEntity)));

var configureMethod = GetType().GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ConfigureDBEntity));
var args = new object[] { modelBuilder };
foreach (var entityType in entityTypes)
    configureMethod.MakeGenericMethod(entityType.ClrType).Invoke(null, args);

我正在尝试实现您建议的解决方案,但我的基本实体采用泛型类型参数(BaseEntity<T>)。因此,当我将typeof(BaseEntity)传递给IsSubclassOf()时,它会出现错误:“Error CS0305使用泛型类型'BaseEntity <T>'需要1个类型参数”。我不确定如何在这种情况下传递带有类型参数的类的类型。任何帮助将不胜感激。谢谢。 - Suhaib Syed
1
@SuhaibSyed,这里有一个例子可以帮到你:https://dev59.com/oVQJ5IYBdhLWcg3wtYLq#53326519。通过 IsBaseEntity 方法,如果不需要 out T 参数,你可以将其省略。 - Ivan Stoev

3
在您的情况下,添加类似于以下内容的东西如何?
    public override int SaveChanges()
    {
        AddTimestamps();
        return base.SaveChanges();
    }

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

    private void AddTimestamps()
    {
        var entities = ChangeTracker.Entries()
            .Where(x => (x.Entity is DBEntity) && (x.State == EntityState.Added || x.State == EntityState.Modified));

        var now = DateTime.UtcNow; // current datetime

        foreach (var entity in entities)
        {
            if (entity.State == EntityState.Added)
            {
                ((DBEntity)entity.Entity).CreatedOn = now;
                ((DBEntity)entity.Entity).EntityStatus = EntityStatus.Created;
            }
            ((DBEntity)entity.Entity).UpdatedOn= now;
        }
    }

您的缺点是不在数据库级别强制执行这些默认值(此时您是数据库无关的),但仍能进行重构。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接