通过多项目架构解决 Entity Framework Core 中的 TPH 继承和迁移问题

问题描述 投票:0回答:1

我正在开发一个多项目架构,模型和数据访问层位于单独的 .csproj 文件中(这些是类库)。使用 EF Core 8 和 TPH 继承,在 VS2022 中运行 Add-Migration 时遇到以下错误:

无法创建“ContentDbContext”类型的“DbContext”。异常“集合导航‘段落’无法添加到实体类型‘摘要’,因为其 CLR 类型‘ICollection’未实现‘IEnumerable’。集合导航必须实现相关实体的 IEnumerable<>。尝试创建实例时抛出。对于设计时支持的不同模式。

这似乎与我的 TPH 配置有关。以下是代码设置的概要。

相关代码

模型接口

public interface IParagraph
{
    string Content { get; set; }
}

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

抽象基类

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

派生类

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

聚合

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

实体配置

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

我尝试过的事情

  • 将外键属性(
    SummaryId
    ParagraphBlockId
    )和导航属性移至基类中。这解决了错误,但破坏了专门化,因为它强制所有派生类共享这些属性。

我需要什么帮助

  • 外键属性:逻辑表明
    ParagraphBase
    不应该知道这些关系。只有派生类(
    SummaryParagraph
    ContentParagraph
    )应该知道它们各自的容器(即
    Summary
    ParagraphBlock
    )。这些派生类应该具有专门的集合(
    ICollection<SummaryParagraph>
    ICollection<ContentParagraph>
    )。但是,EF Core 似乎要求基类了解这些关系,这与预期设计相冲突。这些关系不应该由派生类来管理吗?如果 EF Core 坚持要求基类了解这些关系,为什么会出现这种情况?

这是之前版本的代码,没有出现问题:

相关代码

模型接口
public interface IParent
{
    Guid ParentId { get; set; }
}

public interface IAlineasContainer<T> where T : IAlinea
{
    ICollection<T> Alineas { get; set; }
}

public interface IAlinea
{
    string Content { get; set; }
    Guid? ReplacedById { get; set; }
    Guid? ReplacesId { get; set; }
}
抽象基类
public abstract class AlineaBase : TopicBase, IUtoYearDisplay, IOrderedDisplay, IAlinea
{
    public enum AlineaParentType
    {
        Paragraph,
        Summary
    }
    public string Content { get; set; }
    public Guid? ReplacedById { get; set; }
    public virtual AlineaBase? ReplacedBy { get; set; }
    public Guid? ReplacesId { get; set; }
    public virtual AlineaBase? Replaces { get; set; }
    public int? DisplayEndYearUto { get; set; }
    public int? DisplayStartYearUto { get; set; }
    public int DisplayOrder { get; set; }
    public AlineaParentType ParentType { get; set; }
    public virtual object Parent { get; set; }
}
派生类
public class ParagraphAlinea : AlineaBase
{
    public Guid ParagraphId { get; set; }
    public virtual Paragraph Parent { get; set; }
}

public class SummaryAlinea : AlineaBase
{
    public Guid SummaryId { get; set; }
    public virtual Summary Parent { get; set; }
}
聚合
public class Paragraph : IllustratedTopicSubdivisionBase, IAlineasContainer<ParagraphAlinea>, IDraftable
{
    public bool IsDraft { get; set; } = true;
    public virtual Article Parent { get; set; }
    public virtual ICollection<ParagraphAlinea> Alineas { get; set; }
}

public class Summary : IdentifierModelBase, IAlineasContainer<SummaryAlinea>, IParent, IDraftable, IIllustratable
{
    public Guid ParentId { get; set; }
    public bool IsDraft { get; set; } = true;
    public virtual Topic Parent { get; set; }
    public virtual ICollection<SummaryAlinea> Alineas { get; set; }
    public Guid? IllustrationAssociationId { get; set; }
    public virtual IllustrationAssociation? Illustration { get; set; }
}
实体配置
private void ConfigureEntities(ModelBuilder modelBuilder)
{
    //Other code
    ConfigureAlineaTablePerHierarchy(modelBuilder);
    ConfigureZeroOrOneToManyRelationships(modelBuilder);
    ConfigureOneToOneRelationships(modelBuilder);
}

private void ConfigureAlineaTablePerHierarchy(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<AlineaBase>()
        .HasDiscriminator(x => x.ParentType)
        .HasValue<SummaryAlinea>(AlineaParentType.Summary)
        .HasValue<ParagraphAlinea>(AlineaParentType.Paragraph);
    modelBuilder.Entity<SummaryAlinea>()
        .Property(sa => sa.SummaryId)
        .HasColumnName("SummaryId");
    modelBuilder.Entity<ParagraphAlinea>()
        .Property(pa => pa.ParagraphId)
        .HasColumnName("ParagraphId");
}

private void ConfigureZeroOrOneToManyRelationships(ModelBuilder modelBuilder)
{
    // other code 
    ConfigureZeroOrOneToMany<Summary, SummaryAlinea>(modelBuilder, a => a.Parent, s => s.Alineas, a => a.SummaryId);
    ConfigureZeroOrOneToMany<Paragraph, ParagraphAlinea>(modelBuilder, a => a.Parent, p => p.Alineas, a => a.ParagraphId);
    
    void ConfigureZeroOrOneToMany<TParent, TChild>(ModelBuilder modelBuilder, Expression<Func<TChild, TParent>> parentProperty, Expression<Func<TParent, IEnumerable<TChild>>> childrenProperty, Expression<Func<TChild, object>> foreignKeyProperty, DeleteBehavior deleteBehavior = DeleteBehavior.Restrict)
        where TParent : class
        where TChild : class
    {
        modelBuilder.Entity<TChild>()
            .HasOne(parentProperty)
            .WithMany(childrenProperty)
            .HasForeignKey(foreignKeyProperty)
            .OnDelete(deleteBehavior);
    }
}

实体名称已更改,但本次迭代仍然可操作。


新旧版本之间的重命名:

旧版本 新版本
IAlinea
IParagraph
IAlineasContainer<T>
IHasParagraphs<T>
AlineaBase
ParagraphBase
ParagraphAlinea
SummaryParagraph
SummaryAlinea
ContentParagraph
Paragraph
ParagraphBlock
Summary
Summary
Alineas
(在
Paragraph
Summary
Paragraphs
(在
ParagraphBlock
Summary
AlineaParentType
ParagraphParentType
c# entity-framework-core tph
1个回答
0
投票

在你的帮助下我已经解决了这个问题。谢谢。 这就是我应该编码的内容:

internal class ParagraphBaseConfig : IEntityTypeConfiguration<ParagraphBase>
{
    public void Configure(EntityTypeBuilder<ParagraphBase> builder)
    {
        builder.ToTable("Paragraph");

        builder
            .HasDiscriminator(p => p.ParentType)
            .HasValue<SummaryParagraph>(ParagraphBase.ParagraphParentType.ParagraphBlock)
            .HasValue<SummaryParagraph>(ParagraphBase.ParagraphParentType.Summary);
    }
}

internal class SummaryParagraphConfig : IEntityTypeConfiguration<SummaryParagraph>
{
    public void Configure(EntityTypeBuilder<SummaryParagraph> builder)
    {
        builder.ToTable("Paragraph");
      
        builder
            .HasOne<Summary>()
            .WithMany(s => s.Paragraphs)
            .HasForeignKey(sp => sp.SummaryId)
            .OnDelete(DeleteBehavior.Cascade);

        builder
            .Property(p => p.SummaryId)
            .HasColumnName("SummaryId");
    }
}

internal class ContentParagraphConfig : IEntityTypeConfiguration<ContentParagraph>
{
    public void Configure(EntityTypeBuilder<ContentParagraph> builder)
    {
        builder.ToTable("Paragraph");

        builder
            .HasOne<ParagraphBlock>()
            .WithMany(pb => pb.Paragraphs)
            .HasForeignKey(cp => cp.ParagraphBlockId)
            .OnDelete(DeleteBehavior.Cascade);

        builder
            .Property(p => p.ParagraphBlockId)
            .HasColumnName("ParagraphBlockId");
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.