GetEntityTypes:使用 EF Core 中的通用版本 .属性<TEntity> 配置实体属性
GetEntityTypes: configure entity properties using the generic version of .Property<TEntity> in EF Core
在我的 EF 核心项目中,我有一些实体继承自基础 class DEBentity:
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()。属性(k => k.CreatedOn) 因为它会搞砸所有表。
正如评论中指出的,您可以简单地将字符串常量替换为 nameof(DBEntity.CreatedOn)
等
但是如果你想使用类型化访问器,你可以将基本实体配置代码移动到 generic 方法(通用参数是实际的实体类型)并调用通过反射。
例如,将以下内容添加到您的 DbContext
派生 class 中:
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);
在你的上下文中,添加这样的东西怎么样
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;
}
}
你的缺点是不在数据库级别强制执行这些默认值(此时你是数据库不可知论者)但仍然能够重构
在我的 EF 核心项目中,我有一些实体继承自基础 class DEBentity:
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()。属性(k => k.CreatedOn) 因为它会搞砸所有表。
正如评论中指出的,您可以简单地将字符串常量替换为 nameof(DBEntity.CreatedOn)
等
但是如果你想使用类型化访问器,你可以将基本实体配置代码移动到 generic 方法(通用参数是实际的实体类型)并调用通过反射。
例如,将以下内容添加到您的 DbContext
派生 class 中:
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);
在你的上下文中,添加这样的东西怎么样
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;
}
}
你的缺点是不在数据库级别强制执行这些默认值(此时你是数据库不可知论者)但仍然能够重构