我正在开发一个多项目架构,模型和数据访问层位于单独的 .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);
}
}
实体名称已更改,但本次迭代仍然可操作。
旧版本 | 新版本 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(在 和 ) |
(在 和 ) |
|
|
在你的帮助下我已经解决了这个问题。谢谢。 这就是我应该编码的内容:
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");
}
}