如何将同一列添加到 EF Core 中的所有实体? [英] How to add the same column to all entities in EF Core?

查看:35
本文介绍了如何将同一列添加到 EF Core 中的所有实体?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

想象一下,我想向我的所有实体添加一个 IsDeleted 列或一些审计列.我可以创建一个基类,我的所有实体都将从该基类继承,这将解决我的问题,但是我无法指定列的创建顺序,因此我最终会在实体字段之前获得所有审计字段,这是我不想要的.我希望他们排在桌子的最后.

Imagine that I want to add an IsDeleted colum or some auditing columns to all of my entities. I could create a base class from which all of my entities will inherit and this will solve my problem, however I cannot specify the order in which the column will be created so I will end up with all the auditing fields before the fields of my entity, which I do not want. I want them to be at the end of the table.

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

In the standard version of entity framework we can do this by using annotations that specify the order of the columns. However, such a thing does not exist for EF core at the moment.

我可以在 OnModelCreating() 方法上使用 fluent api 来做到这一点,问题是我只知道如何为我的每个实体单独做这件事,这意味着我必须为我的每个实体编写相同的代码有.

I could do it with the fluent api on the OnModelCreating() method, the problem is that I only know how to do it individually for each of my entities, which means I would have to write the same code for every entity I have.

有什么办法可以通用地为我的所有实体做到这一点?某种 for 循环遍历在我的 dbcontext 上的 DbSets 中注册的所有实体?

Is there any way I can do it generically for all of my entities? Some sort of for loop that iterates through all the entities registered in the DbSets on my dbcontext?

推荐答案

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

Your question title is about adding the same properties to multiple entities. However, you actually know how to achieve this (use a base type) and your actual question is how to ensure that these properties come last in the generated tables' columns.

虽然现在列顺序不重要了,但我将展示一个您可能更喜欢的替代方法,而不是基本类型,并且将公共属性放在表格的末尾.它利用了阴影属性:

Although column order shouldn't really matter nowadays, I'll show an alternative that you may like better than a base type and also positions the common properties at the end of the table. It makes use of shadow properties:

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

Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model.

大多数时候,审计属性在应用程序中不需要太多可见性,所以我认为影子属性正是您所需要的.举个例子:

Most of the times, auditing properties don't need much visibility in the application, so I think shadow properties is exactly what you need. Here's an example:

我有两个班级:

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 与审计属性"混为一谈,虽然它不是一个,它可能需要另一种方法.

As you see: they don't have auditing properties, they're nicely mean and lean POCOs. (By the way, for convenience I lump IsDeleted together with "audit properties", although it isn't one and it may require another approach).

也许这就是这里的主要信息:类模型不会被审计问题所困扰(单一责任),这是 EF 的全部业务.

And maybe that's the main message here: the class model isn't bothered with auditing concerns (single responsibility), it's all EF's business.

审计属性作为影子属性添加.因为我们想为每个实体都这样做,所以我们定义了一个基本的 IEntityTypeConfiguration:

The audit properties are added as shadow properties. Since we want to do that for each entity we define a base 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();
    }
}

具体的配置是从这个基类派生出来的:

The concrete configurations are derived from this base class:

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 中的上下文模型中:

These should be added to the context's model in OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfiguration(new PlanetConfig());
    modelBuilder.ApplyConfiguration(new MoonConfig());
}

这将生成在末尾具有 InsertDateTimeIsDeletedUpdateDateTime 列的数据库表(独立于 base.Configure(builder) 被调用,顺便说一句),尽管按 那个 顺序(字母顺序).我想这已经足够接近了.

This will generate database tables having columns InsertDateTime, IsDeleted and UpdateDateTime at the end (independent of when base.Configure(builder) is called, BTW), albeit in that order (alphabetical). I guess that's close enough.

为了使图片完整,以下是在 SaveChanges 覆盖中完全自动设置值的方法:

To make the picture complete, here's how to set the values fully automatically in a SaveChanges override:

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(),因此排除添加的实体)所以不会有由客户端时钟略微偏离引起的令人困惑的差异.我认为以后更新总是好的.

Small detail: I make sure that when an entity is inserted the database defaults set both fields (see above: ValueGeneratedOnAdd(), and hence the exclusion of added entities) so there won't be confusing differences caused by client clocks being slightly off. I assume that updating will always be well later.

并且要设置 IsDeleted,您可以将此方法添加到上下文中:

And to set IsDeleted you could add this method to the context:

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.

...or turn to one of the proposed mechanisms out there to convert EntityState.Deleted to IsDeleted = true.

这篇关于如何将同一列添加到 EF Core 中的所有实体?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆