Решение проблем наследования и миграции TPH в Entity Framework Core с многопроектной архитектуройC#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Решение проблем наследования и миграции TPH в Entity Framework Core с многопроектной архитектурой

Сообщение Anonymous »

Я работаю над многопроектной архитектурой с моделями и уровнями доступа к данным в отдельных файлах .csproj (это библиотеки классов). Используя EF Core 8 и наследование TPH, я сталкиваюсь со следующей ошибкой при запуске Add-Migration в VS2022:

Невозможно создать «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);
}
Что я пробовал
  • Перемещение свойств внешнего ключа (

    Код: Выделить всё

    SummaryId
    , ParagraphBlockId) и свойства навигации в базовый класс. Это устраняет ошибку, но нарушает специализацию, поскольку заставляет все производные классы использовать эти свойства совместно.
В чем мне нужна помощь
  • Свойства внешнего ключа: Логика предполагает, что ParagraphBase не должен знать об отношениях. Только производные классы (

    Код: Выделить всё

    SummaryParagraph
    и ContentParagraph) должны знать о своих соответствующих контейнерах (т. е. Summary и ParagraphBlock). Эти производные классы должны иметь специализированные коллекции (

    Код: Выделить всё

    ICollection
    и ICollection). Однако EF Core, похоже, требует, чтобы базовый класс знал об отношениях, что противоречит предполагаемому дизайну. Разве отношения не должны вместо этого управляться производными классами? Если EF Core настаивает на том, чтобы базовый класс знал об отношениях, почему это так?
Вот предыдущая версия кода, который работал без проблем:

Соответствующий код

Интерфейсы модели

Код: Выделить всё

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);
}
}
Имена объектов были изменены, но эта итерация осталась работоспособной.

Переименование между старой и новой версией:



Старая версия
Новая Версия



Код: Выделить всё

IParagraph

Код: Выделить всё

IAlineasContainer

Код: Выделить всё

IHasParagraphs

Код: Выделить всё

AlineaBase

Код: Выделить всё

ParagraphBase

Код: Выделить всё

ParagraphAlinea

Код: Выделить всё

SummaryParagraph

Код: Выделить всё

SummaryAlinea

Код: Выделить всё

ContentParagraph

Код: Выделить всё

Paragraph

Код: Выделить всё

ParagraphBlock
(в абзаце и резюме)

Код: Выделить всё

Paragraphs
(в блоке абзацев и сводке)

Код: Выделить всё

AlineaParentType

Код: Выделить всё

ParagraphParentType



Подробнее здесь: https://stackoverflow.com/questions/792 ... e-with-mul
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Решение проблем наследования и миграции TPH в Entity Framework Core с многопроектной архитектурой
    Anonymous » » в форуме C#
    0 Ответы
    12 Просмотры
    Последнее сообщение Anonymous
  • Как запросить все поля модели наследования TPH в Entity Framework Core
    Anonymous » » в форуме C#
    0 Ответы
    9 Просмотры
    Последнее сообщение Anonymous
  • Как запросить все поля модели наследования TPH в ядре фреймворка Entity
    Anonymous » » в форуме C#
    0 Ответы
    13 Просмотры
    Последнее сообщение Anonymous
  • Как использовать префикс пользовательской таблицы в сочетании с TPH в Entity Framework Core?
    Anonymous » » в форуме C#
    0 Ответы
    57 Просмотры
    Последнее сообщение Anonymous
  • Как запрашивать все поля модели наследования TPH в EF Core
    Anonymous » » в форуме C#
    0 Ответы
    9 Просмотры
    Последнее сообщение Anonymous

Вернуться в «C#»