Вопрос или проблема
У меня есть необычная ситуация, с которой я раньше не сталкивался. Контекст – это фреймворк для генерации кода, который я создаю и который хранит независимую от базы данных схему и генерирует всевозможный код на нескольких языках.
Схема сопоставляется с типами .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, сохраняя ясность и управляемость вашего кода. Помните также, что регулярное обновление документации, включая комментарии к коду и изменения схемы, поможет предотвратить путаницу в команде разработки в будущем.