admin管理员组

文章数量:1122846

I typically use the following interfaces for EF Core entities (.NET 9, EF Core 9):

public interface IEntity
    where TEntity: class, new()
{
    // Identity column.
    public long Id { get; set; }
    public bool IsAuditable { get; set; }
    public DateTime DateTimeCreated { get; set; }
    public DateTime? DateTimeModified { get; set; }
}

public interface IEntity<TEntity>:
    IEntity
    where TEntity: class, IEntity, IEntity<TEntity>, new()
{ }

I am now considering using concrete base classes for all entities.

Consider the following hierarchy:

// Acts as a based class for all entities in the DbContext.
public class EntityBase<TEntity>:
    IEntity<TEntity>
    where TEntity : class, IEntity, IEntity<TEntity>, new()
{
    // Identity column.
    public long Id { get; set; }
    public bool IsAuditable { get; set; }
    public DateTime DateTimeCreated { get; set; }
    public DateTime? DateTimeModified { get; set; }
}

public class DocumentBase : EntityBase<DocumentBase>
{
    public string Name { get; set; }
}

public class ElementBase : EntityBase<ElementBase>
{
    public string Name { get; set; }
}

// Should represent a database table with all properties
// from the base class but without a discriminator column.
public class Document : DocumentBase
{
    public virtual ICollection<Element> Elements { get; set; } = [];
}

// Should represent a database table with all properties
// from the base class but without a discriminator column.
public class Element : ElementBase
{
    public long DocumentId { get; set; }
    public virtual Document Document { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Document> Documents { get; set; }
    public DbSet<Element> Elements { get; set; }
}

A couple of things to notice:

  • Neither DocumentBase, nor ElementBase should be required to be abstract (we should have the option to mark them as abstract if needed)
  • The DbContext should know nothing about these base classes except their properties (as if they were declared in the concrete classes)
  • The only commonality between various entities will converge at ConcreteEntity > ConcreteEntityBase > EntityBase. No other paths need to be considered

The resulting tables should have the following declarations WITHOUT a discriminator column (no TPH, TPT):

Tables:

  • Document (Id, IsAuditable, DateTimeCreated, DateTimeModified, Name, Elements)
  • Element (Id, IsAuditable, DateTimeCreated, DateTimeModified, Name, Document, DocumentId)

The best resource I found was this blog discussing Table per Concrete Type (TPC), but it is over a decade old, while I am trying to make sense of EF Core 9.

Please note that this question is not about design choices or whether my approach aligns with best practices. I simply want to know whether this can be achieved with EF9 and, if so, what configuration is needed.

I typically use the following interfaces for EF Core entities (.NET 9, EF Core 9):

public interface IEntity
    where TEntity: class, new()
{
    // Identity column.
    public long Id { get; set; }
    public bool IsAuditable { get; set; }
    public DateTime DateTimeCreated { get; set; }
    public DateTime? DateTimeModified { get; set; }
}

public interface IEntity<TEntity>:
    IEntity
    where TEntity: class, IEntity, IEntity<TEntity>, new()
{ }

I am now considering using concrete base classes for all entities.

Consider the following hierarchy:

// Acts as a based class for all entities in the DbContext.
public class EntityBase<TEntity>:
    IEntity<TEntity>
    where TEntity : class, IEntity, IEntity<TEntity>, new()
{
    // Identity column.
    public long Id { get; set; }
    public bool IsAuditable { get; set; }
    public DateTime DateTimeCreated { get; set; }
    public DateTime? DateTimeModified { get; set; }
}

public class DocumentBase : EntityBase<DocumentBase>
{
    public string Name { get; set; }
}

public class ElementBase : EntityBase<ElementBase>
{
    public string Name { get; set; }
}

// Should represent a database table with all properties
// from the base class but without a discriminator column.
public class Document : DocumentBase
{
    public virtual ICollection<Element> Elements { get; set; } = [];
}

// Should represent a database table with all properties
// from the base class but without a discriminator column.
public class Element : ElementBase
{
    public long DocumentId { get; set; }
    public virtual Document Document { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Document> Documents { get; set; }
    public DbSet<Element> Elements { get; set; }
}

A couple of things to notice:

  • Neither DocumentBase, nor ElementBase should be required to be abstract (we should have the option to mark them as abstract if needed)
  • The DbContext should know nothing about these base classes except their properties (as if they were declared in the concrete classes)
  • The only commonality between various entities will converge at ConcreteEntity > ConcreteEntityBase > EntityBase. No other paths need to be considered

The resulting tables should have the following declarations WITHOUT a discriminator column (no TPH, TPT):

Tables:

  • Document (Id, IsAuditable, DateTimeCreated, DateTimeModified, Name, Elements)
  • Element (Id, IsAuditable, DateTimeCreated, DateTimeModified, Name, Document, DocumentId)

The best resource I found was this blog discussing Table per Concrete Type (TPC), but it is over a decade old, while I am trying to make sense of EF Core 9.

Please note that this question is not about design choices or whether my approach aligns with best practices. I simply want to know whether this can be achieved with EF9 and, if so, what configuration is needed.

Share Improve this question edited Dec 3, 2024 at 1:27 Zhi Lv 21.2k1 gold badge26 silver badges36 bronze badges asked Nov 22, 2024 at 1:56 Raheel KhanRaheel Khan 14.8k14 gold badges86 silver badges170 bronze badges 3
  • 1 ElementBase should be abstract. Do you really want to allow new ElementBase()? It also doesn't really need a generic parameter, since it doesn't use it for anything. Though if you never obtain a EntityTypeBuilder<ElementBase> then it shouldn't be mapped as part of a heirachy. – Jeremy Lakeman Commented Nov 22, 2024 at 2:22
  • @JeremyLakeman: I agree with you considering that the code posted is question-specific. In my case, XxxBase classes would be instantiated, but not in the context of the ORM. I also agree with anyone who thinks that not marking the classes as abstract (i.e. embedding extra functionality in them) is a questionable design choice. – Raheel Khan Commented Nov 22, 2024 at 6:45
  • As for generics, I write code generators where generics prove to be indispensable in certain scenarios. So once again, I agree that I should have left them out of the posted code. I included them since I did not know if they would be relevant to the ORM mapping strategy. It seems not to be the case. – Raheel Khan Commented Nov 22, 2024 at 6:52
Add a comment  | 

1 Answer 1

Reset to default 3

You shouldn't need to define any inheritance configuration for EF to do what you expect, so long as you don't have any DbSet or entity configuration declared for the base class. Any explicit configuration you might need (data types, column naming, etc.) simply do through the end sub-classes, not the common base class.

I have similar base classes for my projects for "Editable" entities which centralizes common fields like CreatedByUserId, CreatedDateTime, etc. The base EnditableEntity does not have a table or any configuration, all configuration is based on the sub-classes. In my case the EditableEntity is abstract as it pretty much should be, but I don't think that directly affects the mapping. What likely would interfere with mapping and have EF expecting a TPH/TPT/TPC config would be any configuration or DbSet referencing the base class directly.

本文标签: inheritanceIgnoring a base type in Entity Framework Core 9 codefirstStack Overflow