Как заставить EF позволять, но игнорировать наследование POCO Entity?

Вопрос или проблема

У меня есть необычная ситуация, с которой я раньше не сталкивался. Контекст – это фреймворк для генерации кода, который я создаю и который хранит независимую от базы данных схему и генерирует всевозможный код на нескольких языках.

Схема сопоставляется с типами .NET и генерирует классы сущностей POCO, среди прочего. Поэтому я использую такие сущности:

Model // Представляет схему / базу данных.
Entity // Представляет сущность / таблицу базы данных.
EntityMember // Представляет свойство / столбец базы данных.
Enumeration // Представляет список значений.
EnumerationMember // Представляет значение в вышеприведенном списке.

Таким образом, каждая модель, сущность, член и т.д. могут иметь несколько атрибутов (System.Attribute), прикрепленных к ним. Поэтому я создал класс для представления атрибутов .NET.

Attribute // Представляет экземпляр атрибута.
AttributeProperty // Представляет свойства, связанные с каждым атрибутом, вместе с их значением, ожидаемым во время компиляции.

Таким образом, вышеописанная структура может легко представлять атрибут, такой как [Description("xyz")].

Теперь мне нужно создать промежуточные таблицы (многие-ко-многим) между атрибутами и другими классами. Проблема в том, что, поскольку объявления атрибутов ожидаются во время компиляции, я не вижу преимуществ от этого подхода.

Вместо этого я рассмотрел возможность реализации пары классов Attribite/AttrbuteProperty отдельно как одно-ко-многим потомка классов Model, Entity, EntityMember и т.д. Мне не важны лучшие практики на данном этапе, но я также не хочу дублировать код, который становится подверженным ошибкам и сложным для поддержки.

Итак, мой вопрос основан на следующей структуре:

public class Model { }
public class Entity { }
public class EntityMember { }
public class Enumeration { }
public class EnumerationMember { }

public abstract class ModelAttribute
{
    public long Id { get; }
    public string Name { get; }
    public string NameAssemblyQualified { get; }
    public Type AttributeTypeClr { get; }
}

public abstract class ModelAttributeProperty
{
    public long Id { get; }
    public string Name { get; }
    public string NameAssemblyQualified { get; }
    public Type AttributeTypeClr { get; }
}

public class ModelEntityAttribute : ModelAttribute
{
    public long Id { get; }
    // ...Свойства, специфичные для этой комбинации...
    public long EntityId { get; }
    public Entity Entity { get; }
}

public class ModelEntityAttributeProperty : ModelAttribute
{
    public long Id { get; }
    // ...Свойства, специфичные для этой комбинации...
    public long ModelEntityAttributeId { get; }
    public Entity ModelEntityAttribute { get; }
}

public class ModelEnumerationAttribute : ModelAttribute
{
    public long Id { get; }
    // ...Свойства, специфичные для этой комбинации...
    public long EnumerationId { get; }
    public Enumeration Enumeration { get; }
}

//... и так далее

Если я сделаю это, EF сопоставит это как TPT, TPH и т.д. Я хочу этого избежать. Как я могу использовать абстрактные классы, заставив EF игнорировать их и упрощать наследование, так сказать? Другими словами, я хочу, чтобы EF вел себя так, как будто базовых классов не существует. Одних только интерфейсов недостаточно для достижения цели наследования в данном случае.

Любая помощь будет оценена.

Ответ или решение

Для решения проблемы использования наследования классов POCO (Plain Old CLR Objects) в Entity Framework (EF), когда требуется игнорировать его, необходимо выполнить несколько шагов. Это поможет избежать нежелательной обработки многопоточности таблиц (TPT), иерархического представления таблиц (TPH) и т.д. Рассмотрим, как правильно настроить вашу модель данных, чтобы EF "игнорировал" абстрактные классы и воспринимал структуру как плоскую.

Шаги для игнорирования наследования в EF

1. Используйте Fluent API

Вы можете использовать Fluent API для настройки конфигурации сущностей, чтобы указать, что EF должен игнорировать базовые абстрактные классы. Этого можно добиться с помощью метода Ignore в классе контекста данных:

public class YourDbContext : DbContext
{
    public DbSet<Model> Models { get; set; }
    public DbSet<Entity> Entities { get; set; }
    public DbSet<EntityMember> EntityMembers { get; set; }
    public DbSet<Enumeration> Enumerations { get; set; }
    public DbSet<EnumerationMember> EnumerationMembers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Ignore<ModelAttribute>();
        modelBuilder.Ignore<ModelAttributeProperty>();

        // Настройка других сущностей
        modelBuilder.Entity<ModelEntityAttribute>().ToTable("ModelEntityAttributes"); 
        modelBuilder.Entity<ModelEntityAttributeProperty>().ToTable("ModelEntityAttributeProperties");  
        modelBuilder.Entity<ModelEnumerationAttribute>().ToTable("ModelEnumerationAttributes");

        base.OnModelCreating(modelBuilder);
    }
}

В этом фрагменте кода методы Ignore позволяют EF не учитывать абстрактные классы ModelAttribute и ModelAttributeProperty, и вместо этого работать только с конкретными реализациями, такими как ModelEntityAttribute и ModelEnumerationAttribute.

2. Рассмотрите использование интерфейсов

Хотя в вашем вопросе упоминается, что интерфейсы не являются предпочтительным решением, они все же могут быть полезны для обеспечения более строгой типизации и гибкого проектирования. В некоторых случаях вы можете создать интерфейсы, чтобы реализовать общую функциональность, которая будет использоваться конкретными классами без необходимости наследования.

3. Не используйте свойства в абстрактных классах

Примером того, как можно организовать код, является избегание хранения свойств в абстрактных классах. Вместо этого определите все свойства в конкретных классах. Например:

public class ModelEntityAttribute : ModelAttribute
{
    public long Id { get; set; }
    // Свойства специфичные для этой комбинации
    public long EntityId { get; set; }
    public Entity Entity { get; set; }
}

public class ModelEntityAttributeProperty
{
    public long Id { get; set; }
    public long ModelEntityAttributeId { get; set; }
    public ModelEntityAttribute ModelEntityAttribute { get; set; }
}

4. Конфигурация атрибутов

Если вы хотите обеспечить атрибуты в классе POCO, вам необходимо сделать так, чтобы EF работал с атрибутами без их явного отображения на модель данных. Например, добавление аннотаций данных, таких как [NotMapped], на поля или свойства можно использовать для указания EF на то, что эти свойства не должны быть связаны с базой данных.

Заключение

Следуя представленным рекомендациям, вы сможете настроить Entity Framework для работы с наследуемыми классами, игнорируя абстрактные классы, что упростит структуру вашей модели данных. Это позволит избежать сложностей с TPT и TPH, сохраняя ясность и управляемость вашего кода. Помните также, что регулярное обновление документации, включая комментарии к коду и изменения схемы, поможет предотвратить путаницу в команде разработки в будущем.

Оцените материал
Добавить комментарий

Капча загружается...