如何将同一列添加到 EF Core 中的所有实体?
Posted
技术标签:
【中文标题】如何将同一列添加到 EF Core 中的所有实体?【英文标题】:How to add the same column to all entities in EF Core? 【发布时间】:2018-08-25 18:27:02 【问题描述】:假设我想向我的所有实体添加一个 IsDeleted 列或一些审计列。我可以创建一个基类,我的所有实体都将从该基类继承,这将解决我的问题,但是我无法指定创建列的顺序,因此我将在我的实体字段之前得到所有审计字段,这是我不想要的。我希望他们排在桌子的最后。
在实体框架的标准版本中,我们可以通过使用指定列顺序的注释来做到这一点。但是,目前 EF 核心不存在这样的事情。
我可以使用 OnModelCreating() 方法上的 fluent api 来完成它,问题是我只知道如何为我的每个实体单独执行此操作,这意味着我必须为我的每个实体编写相同的代码有。
有什么方法可以通用地为我的所有实体执行此操作?某种 for 循环遍历我的 dbcontext 上 DbSets 中注册的所有实体?
【问题讨论】:
您为什么要关心表格中的哪些订单字段? 我也有同样的问题,感觉更像是一个原则问题(我确实理解)但现在我不会打扰。 我也很疑惑你为什么关心顺序,但是本着帮助的精神,你不能只做一个通用方法您的问题标题是关于向多个实体添加相同的属性。但是,您实际上知道如何实现这一点(使用基本类型),而您的实际问题是如何确保这些属性在生成的表的列中排在最后。
虽然现在列顺序并不重要,但我将展示一个您可能更喜欢的替代方法,而不是基本类型,并且也将公共属性放在表格的末尾。它利用shadow properties:
影子属性是未在 .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
和 UpdateDateTime
的数据库表(与何时调用 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>(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 类中还是在数据库中? @JohnyL 两者。例如,请参阅this。在旧版本的 Sql Server(我相信是 Oracle)中,BLOB 列的位置确实很重要。这可能是复制中的一个问题,但我认为这不是 OP 所考虑的。 那么这是否意味着你需要为每个实体创建一个新的配置文件,而不是大约相同数量的代码@GertArnold @csharpdudeni77 与...相比,数量相同?【参考方案2】:您始终可以为模型生成初始迁移并手动重新排列迁移中的列顺序。
这是 EF Core 中对显式列排序的开放问题跟踪支持:https://github.com/aspnet/EntityFrameworkCore/issues/10059
另请参阅有关使用影子属性和查询过滤器进行软删除的问题和答案。 EF Core: Soft delete with shadow properties and query filters
【讨论】:
以上是关于如何将同一列添加到 EF Core 中的所有实体?的主要内容,如果未能解决你的问题,请参考以下文章
EF Core 在对现有查询添加查询时附加所有实体 [重复]