Home > front end >  How to to handle abstract base class in entity framework(code first)?
How to to handle abstract base class in entity framework(code first)?

Time:12-29

I am implementing an audit functionality to keep track of any changes (create, update, delete) made to any type of object. For this I need a way to declare a generic object which can point to object of any other class derived from an abstract base class.

Base class I do not want to have table for in database :

  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()");
    }
  }

and a couple of models derived from base for example :

  [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);

    // ...
  }

Now my failed attempt to create the Audit model

  [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);
    // ...
  }

When I try to create a migration for audit class, I get below error

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

Here is my Db Context if needed

  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());
    }
  }

CodePudding user response:

try to remove [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute from public DateTime CreationDateTime propert of EntityBase.

Also I would remove

public EntityBase Entity { get; set; }  

from Audit class , you would not be able to use it. Or you can try to make it not mapped

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

CodePudding user response:

By this Entity property in Audit, EF takes a completely different path in implementing the model.

  • Without it, it maps each entity to its own independent table, each table having all columns including the ones in EntityBase. In fact, it completely ignores EntityBase. As for EF, there is no inheritance.

  • If it's there, EF recognizes that Audit needs a foreign key to any type derived from EntityBase. In order to implement this polymorphic association, a base table is required that "collects" all primary key values of EntityBase entities. Audit refers to this base table's primary key.

    Also, table EntityBase contains all shared properties (CreationDateTime etc), and the separate entity table only have their own properties and a primary key that's also a foreign key to EntityBase. This is referred to as Table per Type (TPT) inheritance.

This TPT inheritance also requires EntityBase to have the primary key property, implying that the inheriting entities shouldn't.

All in all, if you remove the key properties from the inheriting entities and add

public int ID { get; set; }

to EntityBase, EF will be able to map your class model.

However, be aware of the ins and outs of TPT inheritance, esp. the warning

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

And, of course, more so compared to no inheritance.

  • Related