您的问题标题是关于向多个实体添加相同的属性。但是,您实际上知道如何实现这一点(使用基本类型)并且您的actual问题是如何确保这些属性位于生成的表列的最后。
尽管现在列顺序并不重要,但我将展示一个您可能比基本类型更喜欢的替代方案,并且also将公共属性放置在表的末尾。它利用阴影属性:
影子属性是未在 .NET 实体类中定义但在 EF Core 模型中为该实体类型定义的属性。
大多数时候,审核属性在应用程序中不需要太多可见性,因此我认为影子属性正是您所需要的。这是一个例子:
我有两节课:
public class Planet
{
public Planet()
{
Moons = new HashSet<Moon>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Moon> Moons { get; set; }
}
public class Moon
{
public int ID { get; set; }
public int PlanetID { get; set; }
public string Name { get; set; }
public Planet Planet { get; set; }
}
正如您所看到的:他们没有审计属性,他们是非常刻薄和精干的 POCO。 (顺便说一句,为了方便起见,我把IsDeleted
与“审核属性”一起使用,尽管它不是一个并且可能需要另一种方法)。
也许这就是这里的主要信息:类模型不关心审计问题(单一职责),都是 EF 的事。
审核属性作为影子属性添加。由于我们想为每个实体执行此操作,因此我们定义了一个基础IEntityTypeConfiguration
:
public abstract class BaseEntityTypeConfiguration<T> : IEntityTypeConfiguration<T>
where T : class
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property<bool>("IsDeleted")
.IsRequired()
.HasDefaultValue(false);
builder.Property<DateTime>("InsertDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
builder.Property<DateTime>("UpdateDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
}
}
具体配置源自该基类:
public class PlanetConfig : BaseEntityTypeConfiguration<Planet>
{
public override void Configure(EntityTypeBuilder<Planet> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
// Follows the default convention but added to make a difference :)
builder.HasMany(p => p.Moons)
.WithOne(m => m.Planet)
.IsRequired()
.HasForeignKey(m => m.PlanetID);
base.Configure(builder);
}
}
public class MoonConfig : BaseEntityTypeConfiguration<Moon>
{
public override void Configure(EntityTypeBuilder<Moon> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
base.Configure(builder);
}
}
这些应该添加到上下文的模型中OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
这将生成具有列的数据库表InsertDateTime
, IsDeleted
and UpdateDateTime
最后(与何时base.Configure(builder)
被称为,顺便说一句),尽管在that顺序(按字母顺序)。我想这已经足够接近了。
为了使图片更完整,以下是如何在SaveChanges
覆盖:
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries()
.Where(e => e.Properties.Any(p => p.Metadata.Name == "UpdateDateTime")
&& e.State != Microsoft.EntityFrameworkCore.EntityState.Added))
{
entry.Property("UpdateDateTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
小细节:我确保插入实体时数据库默认设置两个字段(见上文:ValueGeneratedOnAdd()
,因此排除了添加的实体),因此不会因客户端时钟稍微偏离而导致令人困惑的差异。我认为更新总是会晚一些。
并设置IsDeleted
您可以将此方法添加到上下文中:
public void MarkForDelete<T>(T entity)
where T : class
{
var entry = this.Entry(entity);
// TODO: check entry.State
if(entry.Properties.Any(p => p.Metadata.Name == "IsDeleted"))
{
entry.Property("IsDeleted").CurrentValue = true;
}
else
{
entry.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
}
}
...或者转向建议的机制之一进行转换EntityState.Deleted
to IsDeleted = true
.