如何在 entity framework 中处理抽象基础 class(代码优先)?

How to to handle abstract base class in entity framework(code first)?

我正在实施审计功能以跟踪对任何类型的对象所做的任何更改(创建、更新、删除)。为此,我需要一种方法来声明一个通用对象,该对象可以指向从抽象基 class.

派生的任何其他 class 的对象

Base class 我不想在数据库中使用 table :

  public abstract class EntityBase {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [DataType(DataType.Date)]
    public DateTime CreationDateTime { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [DataType(DataType.Date)]
    public DateTime ModificationDateTime { get; set; }
  }

  public class EntityBaseConfigurations<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : EntityBase {
    public virtual void Configure(EntityTypeBuilder<TEntity> builder) {
      builder.Property(e => e.CreationDateTime).IsRequired().HasDefaultValueSql("GETUTCDATE()");
      builder.Property(e => e.ModificationDateTime).IsRequired().HasDefaultValueSql("GETUTCDATE()");
    }
  }

以及从 base 派生的几个模型,例如:

  [Table("Initiative")]
  public class Initiative : EntityBase {
    [Key]
    public int InitiativeId { get; set; }

    // ...

    [Required]
    public Contributor Assignee { get; set; }
  }

  public class InitiativeConfiguration : EntityBaseConfigurations<Initiative> {
    public override void Configure(EntityTypeBuilder<Initiative> builder) => base.Configure(builder);

    // ...
  }
  [Table("Contributor")]
  public class Contributor : EntityBase {
    [Key]
    public int ContributorId { get; set; }

    // ...
  }

  public class ContributorConfiguration : EntityBaseConfigurations<Contributor> {
    public override void Configure(EntityTypeBuilder<Contributor> builder) => base.Configure(builder);

    // ...
  }

现在我尝试创建审计模型失败了

  [Table("Audit")]
  public class Audit : EntityBase {
    [Key]
    public int AuditId { get; set; }

    public User Actor {get; set;}

    // ...

    public EntityBase Entity { get; set; } // I want to be able to point to objct of any class derived from EntityBase (ex. Initiative, Contributor)
  }

  public class AuditConfiguration : EntityBaseConfigurations<Audit> {
    public override void Configure(EntityTypeBuilder<Audit> builder) => base.Configure(builder);
    // ...
  }

当我尝试为审核创建迁移时 class,出现以下错误

The derived type 'Audit' cannot have KeyAttribute on property 'AuditId' since primary key can only be declared on the root type.

如果需要,这是我的数据库上下文

  public class DbContext : IdentityDbContext<User> {
    public DbContext(DbContextOptions<DbContext> options) : base(options) { }

    public DbSet<Initiative> Initiatives { get; set; }
    public DbSet<Contributor> Contributors { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
      base.OnModelCreating(modelBuilder);
      modelBuilder.ApplyConfiguration(new InitiativeConfiguration());
      modelBuilder.ApplyConfiguration(new ContributorConfiguration());
    }
  }

尝试从 EntityBase 的 public DateTime CreationDateTime 属性中删除 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 属性。

我也会删除

public EntityBase Entity { get; set; }  

来自审计 class ,您将无法使用它。或者你可以尝试让它不被映射

[NotMapped]
public EntityBase Entity { get; set; }  

通过Entity 属性 in Audit,EF在实现模型上采取了完全不同的路径。

  • 没有它,它会将每个实体映射到其自己独立的 table,每个 table 具有所有列,包括 EntityBase 中的列。事实上,它完全忽略了EntityBase。至于EF,没有继承。

  • 如果存在,EF 会识别出 Audit 需要从 EntityBase 派生的 any 类型的外键。为了实现这个多态关联,需要一个基础table来“收集”EntityBase实体的所有主键值。 Audit 指的是这个基 table 的主键。

    此外,table EntityBase 包含所有共享属性(CreationDateTime 等),而单独的实体 table 只有自己的属性 and 也是 EntityBase 的外键的主键。这被称为 Table 每个类型 (TPT) 继承。

此 TPT 继承还要求 EntityBase 具有主键 属性,这意味着继承实体不应该。

总而言之,如果您从继承实体中删除关键属性并添加

public int ID { get; set; }

EntityBase,EF 将能够映射您的 class 模型。

但是,请注意 ins and outs of TPT inheritance,尤其是。警告

In many cases, TPT shows inferior performance when compared to TPH.

当然,与没有继承相比,更是如此。