如何将相同的列添加到EF Core中的所有实体?

想象一下,我想在我的所有实体中添加一个IsDeleted列或一些审核列。 我可以创建一个基类,我的所有实体都将从该基类inheritance,这将解决我的问题,但是我无法指定创建列的顺序,因此我将在实体的字段之前得到所有审计字段,我不想要。 我希望他们在桌子的尽头。

在标准版本的entity framework中,我们可以使用指定列顺序的注释来完成此操作。 但是,目前EF核心并不存在这样的事情。

我可以使用OnModelCreating()方法上的流畅api来做,问题是我只知道如何为我的每个实体单独执行它,这意味着我必须为我拥有的每个实体编写相同的代码。

对于我的所有实体,我有什么办法可以做到这一点吗? 某种for循环迭代遍历dbcontext上DbSets中注册的所有实体?

您的问题标题是关于向多个实体添加相同的属性。 但是,您实际上知道如何实现这一点(使用基类型),您的实际问题是如何确保这些属性在生成的表的列中最后。

虽然列顺序现在不应该真正重要,但我会展示一种替代方案,你可能比基类型更好,并且还将公共属性放在表的末尾。 它使用阴影属性

阴影属性是未在.NET实体类中定义的属性,但是在EF Core模型中为该实体类型定义。

大多数情况下,审计属性在应用程序中不需要太多可见性,因此我认为阴影属性正是您所需要的。 这是一个例子:

我有两节课:

public class Planet { public Planet() { Moons = new HashSet(); } public int ID { get; set; } public string Name { get; set; } public virtual ICollection 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与“审计属性”一起整理,虽然它不是一个,它可能需要另一种方法)。

也许这就是这里的主要信息:class级模型不受审计问题的困扰( 单一责任 ),这都是EF的业务。

审计属性将添加为阴影属性。 由于我们希望为每个实体执行此操作,因此我们定义了一个基本的IEntityTypeConfiguration

 public abstract class BaseEntityTypeConfiguration : IEntityTypeConfiguration where T : class { public virtual void Configure(EntityTypeBuilder builder) { builder.Property("IsDeleted") .IsRequired() .HasDefaultValue(false); builder.Property("InsertDateTime") .IsRequired() .HasDefaultValueSql("SYSDATETIME()") .ValueGeneratedOnAdd(); builder.Property("UpdateDateTime") .IsRequired() .HasDefaultValueSql("SYSDATETIME()") .ValueGeneratedOnAdd(); } } 

具体配置派生自此基类:

 public class PlanetConfig : BaseEntityTypeConfiguration { public override void Configure(EntityTypeBuilder 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 { public override void Configure(EntityTypeBuilder 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()); } 

这将生成在末尾具有InsertDateTimeIsDeletedUpdateDateTime列的数据库表(独立于base.Configure(builder) BTW),尽管base.Configure(builder)顺序(按字母顺序排列)。 我猜这很接近。

为了使图片完整,以下是如何在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 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转换为IsDeleted = true

您始终可以为模型生成初始迁移,并在迁移中手动重新排列列顺序。

以下是对EF Core中显式列排序的开放式问题跟踪支持: https : //github.com/aspnet/EntityFrameworkCore/issues/10059

另请参阅有关使用“阴影属性”和“查询filter”进行软删除的问题和答案。 EF Core:使用阴影属性和查询filter进行软删除