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

时间:2018-08-25 18:27:03

标签: c# entity-framework .net-core entity-framework-core

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

在标准版本的实体框架中,我们可以通过使用指定列顺序的注释来实现。但是,目前对于EF核心还不存在这种情况。

我可以使用OnModelCreating()方法上的流利api来做到这一点,问题是我只知道如何分别为我的每个实体进行操作,这意味着我必须为每个实体编写相同的代码有。

有什么方法可以对我所有的实体通用吗?某种for循环遍历我在dbcontext上DbSet中注册的所有实体?

3 个答案:

答案 0 :(得分:5)

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

尽管如今列顺序实际上并不重要,但我将展示一种替代方法,您可能比基本类型更喜欢,并且将公用属性放在表的末尾。它利用了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());
}

这将生成末尾具有列InsertDateTimeIsDeletedUpdateDateTime的数据库表(与调用base.Configure(builder)的时间有关,BTW),尽管在顺序(字母顺序)。我想这已经足够了。

要使图片完整,请按以下步骤自动设置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

答案 1 :(得分:2)

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

以下是对EF Core https://github.com/aspnet/EntityFrameworkCore/issues/10059

中的显式列排序的公开问题跟踪支持。

有关使用阴影属性和查询过滤器进行软删除的信息,请参见此问题和答案。 EF Core: Soft delete with shadow properties and query filters

答案 2 :(得分:0)

带有简单示例的说明。

如果我们要在多个/所有表中使用同一列,则可以使用Shadow属性。您可以一次在所有实体上配置阴影属性,而不必为所有实体手动配置它们。

例如,我们可以一次在所有实体上配置python -c "import time; time.sleep(100)" [[ $? -ne 0 ]] && exit echo END CreatedDate,如下所示。

UpdatedDate