Joã*_*iva 1 c# entity-framework entity-framework-core .net-core
想象一下,我想向所有实体添加一个IsDeleted列或一些审核列。我可以创建一个基类,所有实体都将从该基类继承,这将解决我的问题,但是我无法指定创建列的顺序,因此我将以所有审计字段结束,然后才是实体字段,我不想要。我希望他们在桌子的尽头。
在实体框架的标准版本中,我们可以通过使用指定列顺序的注释来做到这一点。但是,目前对于EF核心还不存在这种情况。
我可以使用OnModelCreating()方法上的流利api来做到这一点,问题在于我只知道如何为我的每个实体单独进行操作,这意味着我必须为我拥有的每个实体编写相同的代码。
有什么办法可以为我所有的实体通用吗?某种for循环遍历我在dbcontext的DbSet中注册的所有实体?
您的问题标题是关于将相同的属性添加到多个实体。但是,您实际上知道如何实现此目标(使用基本类型),并且实际的问题是如何确保这些属性在生成的表的列中排在最后。
尽管如今列顺序实际上并不重要,但我将展示一种替代方法,您可能比基本类型更喜欢,并且还将公用属性放在表的末尾。它利用了阴影属性:
阴影属性是在.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; }
}
Run Code Online (Sandbox Code Playgroud)
如您所见:它们没有审核属性,它们的意思是卑鄙且精简的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();
}
}
Run Code Online (Sandbox Code Playgroud)
具体配置是从该基类派生的:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这些应该添加到上下文的模型中OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
Run Code Online (Sandbox Code Playgroud)
这将生成的数据库表具有列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();
}
Run Code Online (Sandbox Code Playgroud)
小细节:我确保在插入实体时,数据库默认值同时设置了两个字段(请参见上文: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;
}
}
Run Code Online (Sandbox Code Playgroud)
...或转向提出的一种机制以转换EntityState.Deleted为IsDeleted = true。
| 归档时间: |
|
| 查看次数: |
1278 次 |
| 最近记录: |