Resolving TPH Inheritance and Migration Issues in Entity Framework Core with Multi-Project Architecture

Laurent Guigon 311 Reputation points
2024-12-06T10:52:46.85+00:00

Hello,

I’m working on a multi-project architecture with models and data access layers in separate .csproj files (These are Classes Libraries.) Using EF Core 8 and TPH inheritance, I encounter the following error when running Add-Migration in VS2022:

Unable to create a 'DbContext' of type 'ContentDbContext'. The exception 'The collection navigation 'Paragraphs' cannot be added to the entity type 'Summary' because its CLR type 'ICollection<SummaryParagraph>' does not implement 'IEnumerable<ParagraphBase>'. Collection navigations must implement IEnumerable<> of the related entity.'

This seems related to my TPH configuration. Below is an outline of the code setup.


Relevant Code

Model Interfaces

public interface IParagraph
{
 string Content { get; set; }
} 
public interface IHasParagraphs<T> where T : IParagraph
{
 ICollection<T> Paragraphs { get; set; }
} 

Abstract Base Class

public abstract class ParagraphBase : TopicStructureBase, IOrderable, IParagraph
{
    public enum ParagraphParentType { ParagraphBlock, Summary }
    public ParagraphParentType? ParentType { get; set; }
    public int DisplayOrder { get; set; }
    public string Content { get; set; } = string.Empty;
}

Derived Classes

public class SummaryParagraph : ParagraphBase
{
    public Guid SummaryId { get; set; }
    public virtual Summary Summary { get; set; } = new();
}

public class ContentParagraph : ParagraphBase
{
    public Guid ParagraphBlockId { get; set; }
    public virtual ParagraphBlock Block { get; set; } = new();
}

Aggregates

public class Summary : IdentifiableEntityBase, IHasParagraphs<SummaryParagraph>
{
    public virtual ICollection<SummaryParagraph> Paragraphs { get; set; } = new HashSet<SummaryParagraph>();
}

public class ParagraphBlock : TopicElementBase, IHasParagraphs<ContentParagraph>
{
    public virtual ICollection<ContentParagraph> Paragraphs { get; set; } = new HashSet<ContentParagraph>();
}

Entity Configuration

public void Configure(EntityTypeBuilder<ParagraphBase> builder)
{
    builder.HasDiscriminator(p => p.ParentType)
           .HasValue<ContentParagraph>(ParagraphParentType.ParagraphBlock)
           .HasValue<SummaryParagraph>(ParagraphParentType.Summary);

    builder.HasOne<Summary>()
           .WithMany(s => s.Paragraphs)
           .HasForeignKey(sp => ((SummaryParagraph)sp).SummaryId);

    builder.HasOne<ParagraphBlock>()
           .WithMany(pb => pb.Paragraphs)
           .HasForeignKey(cp => ((ContentParagraph)cp).ParagraphBlockId);
}

What I’ve Tried

  1. Moving foreign key properties (SummaryId, ParagraphBlockId) and navigation properties into the base class. This resolves the error but breaks specialization, as it forces all derived classes to share these properties.
  2. Simplifying the configuration by defining relationships directly in OnModelCreating. This avoids IEntityTypeConfiguration<T> but does not address the root issue.
  3. Searching online and using tools like ChatGPT, but without finding a definitive answer.

What I Need Help With

  1. Foreign Key Properties: Must SummaryId and ParagraphBlockId be in the base class for TPH to work? If so, why does EF Core require this?
  2. Compatibility Issues: Are there changes in EF Core 9 that could affect this configuration?
  3. Alternative Approaches: Is there a way to resolve this issue while maintaining proper separation of concerns?

Developer technologies .NET Entity Framework Core
{count} votes

2 answers

Sort by: Most helpful
  1. Anonymous
    2024-12-11T03:39:04.9133333+00:00

    Hi, @Laurent Guigon.

    Foreign key properties

    Foreign key properties (such as SummaryId and ParagraphBlockId) do not necessarily need to be in the base class. The official documentation on TPH does not clearly state the requirements for foreign keys.

    Compatibility issues

    No compatibility issues with EF Core 9 have been found so far. EF Core 9 does have major adjustments to TPH, but it affects Azure Cosmos DB. After testing the rollback version, the problem still occurs.

    Alternative methods:

    No better alternative methods have been found without modifying the table structure.

    Currently available directions to try:

    1. Do not use TPH, directly set the EntityConfig of ContentParagraph and SummaryParagraph, and the properties of the subclass inheriting the parent class will still be generated in the database. However, due to the relationship between foreign keys, you will get an error when executing dotnet ef database update, so you still need to adjust the design of foreign keys.
    2. Re-adjust the table structure. If the table itself needs to implement a one-to-many relationship, consider using an attribute to save the associated Id.

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Laurent Guigon 311 Reputation points
    2024-12-13T11:50:04.3966667+00:00

    This is what I Should code :

    internal
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.