Невозможно создать «DbContext» типа «ContentDbContext». Исключение «Навигация по коллекции «Абзацы» не может быть добавлено к типу сущности «Сводка», поскольку его тип CLR «ICollection» не реализует «IEnumerable». Навигация по коллекциям должна реализовывать IEnumerable связанной сущности.' был выброшен при попытке создать экземпляр. Для различных шаблонов, поддерживаемых во время разработки.
Похоже, это связано с моей конфигурацией TPH. Ниже приведена схема настройки кода.
Соответствующий код
Интерфейсы модели
Код: Выделить всё
public interface IParagraph
{
string Content { get; set; }
}
public interface IHasParagraphs where T : IParagraph
{
ICollection 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
{
public virtual ICollection Paragraphs { get; set; } = new HashSet();
}
public class ParagraphBlock : TopicElementBase, IHasParagraphs
{
public virtual ICollection Paragraphs { get; set; } = new HashSet();
}
Конфигурация объекта
Код: Выделить всё
public void Configure(EntityTypeBuilder builder)
{
builder.HasDiscriminator(p => p.ParentType)
.HasValue(ParagraphParentType.ParagraphBlock)
.HasValue(ParagraphParentType.Summary);
builder.HasOne()
.WithMany(s => s.Paragraphs)
.HasForeignKey(sp => ((SummaryParagraph)sp).SummaryId);
builder.HasOne()
.WithMany(pb => pb.Paragraphs)
.HasForeignKey(cp => ((ContentParagraph)cp).ParagraphBlockId);
}
- Перемещение свойств внешнего ключа (, ParagraphBlockId) и свойства навигации в базовый класс. Это устраняет ошибку, но нарушает специализацию, поскольку заставляет все производные классы использовать эти свойства совместно.
Код: Выделить всё
SummaryId
- Свойства внешнего ключа: Логика предполагает, что ParagraphBase не должен знать об отношениях. Только производные классы (и ContentParagraph) должны знать о своих соответствующих контейнерах (т. е. Summary и ParagraphBlock). Эти производные классы должны иметь специализированные коллекции (
Код: Выделить всё
SummaryParagraph
и ICollection). Однако EF Core, похоже, требует, чтобы базовый класс знал об отношениях, что противоречит предполагаемому дизайну. Разве отношения не должны вместо этого управляться производными классами? Если EF Core настаивает на том, чтобы базовый класс знал об отношениях, почему это так?Код: Выделить всё
ICollection
Соответствующий код
Интерфейсы моделиКод: Выделить всё
public interface IParent
{
Guid ParentId { get; set; }
}
public interface IAlineasContainer where T : IAlinea
{
ICollection 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, IDraftable
{
public bool IsDraft { get; set; } = true;
public virtual Article Parent { get; set; }
public virtual ICollection Alineas { get; set; }
}
public class Summary : IdentifierModelBase, IAlineasContainer, IParent, IDraftable, IIllustratable
{
public Guid ParentId { get; set; }
public bool IsDraft { get; set; } = true;
public virtual Topic Parent { get; set; }
public virtual ICollection 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()
.HasDiscriminator(x => x.ParentType)
.HasValue(AlineaParentType.Summary)
.HasValue(AlineaParentType.Paragraph);
modelBuilder.Entity()
.Property(sa => sa.SummaryId)
.HasColumnName("SummaryId");
modelBuilder.Entity()
.Property(pa => pa.ParagraphId)
.HasColumnName("ParagraphId");
}
private void ConfigureZeroOrOneToManyRelationships(ModelBuilder modelBuilder)
{
// other code
ConfigureZeroOrOneToMany(modelBuilder, a => a.Parent, s => s.Alineas, a => a.SummaryId);
ConfigureZeroOrOneToMany(modelBuilder, a => a.Parent, p => p.Alineas, a => a.ParagraphId);
void ConfigureZeroOrOneToMany(ModelBuilder modelBuilder, Expression parentProperty, Expression childrenProperty, Expression foreignKeyProperty, DeleteBehavior deleteBehavior = DeleteBehavior.Restrict)
where TParent : class
where TChild : class
{
modelBuilder.Entity()
.HasOne(parentProperty)
.WithMany(childrenProperty)
.HasForeignKey(foreignKeyProperty)
.OnDelete(deleteBehavior);
}
}
Переименование между старой и новой версией:
Старая версия
Новая Версия
Код: Выделить всё
IAlinea
Код: Выделить всё
IParagraph
Код: Выделить всё
IAlineasContainer
Код: Выделить всё
IHasParagraphs
Код: Выделить всё
AlineaBase
Код: Выделить всё
ParagraphBase
Код: Выделить всё
ParagraphAlinea
Код: Выделить всё
SummaryParagraph
Код: Выделить всё
SummaryAlinea
Код: Выделить всё
ContentParagraph
Код: Выделить всё
Paragraph
Код: Выделить всё
ParagraphBlock
Код: Выделить всё
Summary
Код: Выделить всё
Summary
Код: Выделить всё
Alineas
Код: Выделить всё
Paragraphs
Код: Выделить всё
AlineaParentType
Код: Выделить всё
ParagraphParentType
Подробнее здесь: https://stackoverflow.com/questions/792 ... e-with-mul