2015-09-15 3 views
8

Чтобы избежать использования таблицы для иерархии (TPH), я рассматривал примеры того, как лучше всего реализовать Table-Per-Concrete Class (TPC) в моей модели базы данных. Я наткнулся на official documentation и this article.Наследование на конкретный тип (TPC) в Entity Framework 6 (EF6)

Ниже приведены некоторые макеты классов с некоторым простым наследованием.

public class BaseEntity 
{ 
    public BaseEntity() 
    { 
     ModifiedDateTime = DateTime.Now; 
    } 

    [Key] 
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] 
    public int Id { get; set; } 

    public DateTime ModifiedDateTime { get; set; } 
} 

public class Person : BaseEntity 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
} 

public class Business : BaseEntity 
{ 
    public string Name { get; set; } 
    public string Location { get; set; } 
} 

Конфигурации DbModelBuilder, используемые в примерах в обеих статьях.

modelBuilder.Entity<BaseEntity>() 
    .Property(c => c.Id) 
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

modelBuilder.Entity<Person>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Person"); 
}); 

modelBuilder.Entity<Business>().Map(m => 
{ 
    m.MapInheritedProperties(); 
    m.ToTable("Business"); 
}); 

Приложение работает успешно, но когда я иду обратно в базу данных я нахожу три (3) таблицы вместо двух (2) Я ожидал найти. После небольшого тестирования появится таблица «BaseEntity», но она никогда не используется. Кажется, все работает отлично, за исключением пустой пустой оси.

Я общаюсь с конфигурациями DbModelBuilder, в итоге удаляя конфигурации «BaseEntity», которые обеспечивают ожидаемый результат; Две (2) таблицы, каждая из которых имеет правильные свойства и работает правильно.

Я делаю последнее испытание, уничтожаю все конфигурации DbModelBuilder, включаю только два свойства (2) DbSet для «Личность» и «Бизнес» и снова тестирую.

public DbSet<Person> People { get; set; } 
public DbSet<Business> Businesses { get; set; } 

К моему удивлению, проект строит, выходит в базу данных, создает только две таблицы со всеми свойствами класса, включая унаследованные от них из класса «BaseEntity». Я могу делать операции CRUD без проблем.

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

Изменения в базе данных были успешно завершены, но при обновлении контекста объекта произошла ошибка . Объект ObjectContext может быть в несогласованном состоянии. Внутреннее сообщение об исключении: AcceptChanges не может продолжаться, потому что ключевые значения объекта конфликтуют с другим объектом в ObjectStateManager. Перед вызовом AcceptChanges убедитесь, что значения клавиш равны .

  1. Мне любопытно, почему примеры используют свойство MapInheritedProperties; это устаревший метод?
  2. Почему оба примера говорят о включении свойств конфигурации для «BaseEntity», но в то же время они включают либо свойство DbSet, либо любые конфигурации DbModelBuilder для класса «BaseEntity» заставляют создать неиспользуемую таблицу.
  3. В отношении уникальной ключевой ошибки, о которой предупреждали статьи; Я не могу воспроизвести ошибку, и я много раз тестировал первичный ключ как int, сгенерированный базой данных, так и директив, сгенерированный базой данных. Является ли информация об этой ошибке также устаревшей или есть тест, который я могу запустить, чтобы произвести указанную ошибку?
+0

Я думаю, что должно быть создано 3 таблицы, хотя базовый кажется непригодным. это некоторые недостатки TPC, вы можете воспользоваться некоторыми преимуществами в клиентском коде, но должны пострадать от некоторых избыточных данных (которые не должны быть даже созданы, как в традиционной концепции проектирования баз данных). Я думаю, вы можете попробовать TPT, тогда как базовая таблица содержит все общие столбцы, и это больше не будет считаться избыточным. – Hopeless

+1

@Hopeless Причина, по которой я не рассматривал TPT, насколько я обожаю дизайн, являющийся OCD, как и я, - это производительность, которую вы выбрали из всех операторов соединения, необходимых для отвлечения даже немного сложных данных. Что касается утверждения о трех таблицах, я видел много статей об обратном, но опять-таки я не смог нормально работать. Мне интересно, есть ли какой-то недостаток для окончательного способа настройки моделей; Я столкнулся с двумя таблицами, без ненужных таблиц, без ненужных столбцов, с первичными ключевыми проблемами, но не могу найти примеров, которые я делал. – Nicholas

+0

Для TPH класс BaseEntity должен быть абстрактным. Я думаю, что только один должен генерировать только два таблицы DB для конкретных типов. Я действительно столкнулся с тем, что этот объект ObjectContext находится в ошибке несогласованного состояния, и единственным способом, который я смог исправить, является создание поля Id в типе BaseEntity Guid вместо Int (что кажется довольно расточительным). – blgrnboy

ответ

2

Просто, чтобы сделать это все проще, я переместил код, необходимый, чтобы заставить TablePerConcrete открывать исходный код.Его цель - разрешить функции, обычно доступные только в Fluent Interface (где вам нужно разбросать много кода в свой метод OnModelCreating класса Db), чтобы перейти к функциям на основе атрибутов.

Это позволяет делать такие вещи, как это:

[TablePerConcrete] 
public class MySubclassTable : MyParentClassEntity 

Принуждение TPC независимо от того, что EF может принять решение вывести из родительского класса/подкласса отношений.

Одна из интересных проблем заключается в том, что иногда EF может испортить унаследованное свойство Id, установив его заполненным явным значением, а не генерируемым базой данных. Вы можете убедиться, что это не делает это, если интерфейс родительского класса реализует IId (который просто говорит: у этого есть свойство Id), затем маркировка подклассов [ForcePKId].

public class MyParentClassEntity : IId 
{ 
    public int Id { get; set; } 
    . . . 

[TablePerConcrete] 
[ForcePKId] 
public class MySubclassTable : MyParentClassEntity 
{ 
    // No need for PK/Id property here, it was inherited and will work as 
    // you intended. 

Пинающ код, который обрабатывает все это для вас очень просто - добавить пару строк в класс Db:

public class Db : DbContext 
{ 
    . . . 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     var modelsProject = Assembly.GetExecutingAssembly(); 
     B9DbExtender.New().Extend(modelBuilder, modelsProject); 

Вы можете получить к нему доступ один из 2-х способов:

  1. Via одной сути со всеми соответствующими классами копии вставил в один файл, здесь: https://gist.github.com/b9chris/8efd30687d554d1ceeb3fee359c179f9

  2. Через библиотеку, нашу Brass9.Data, которую мы выпускаем с открытым исходным кодом. В нем есть много других инструментов EF6, таких как Data Migrations. Это также более организованным, с классами вспыхнувших в отдельные файлы, как вы обычно ожидаете: https://github.com/b9chris/Brass9.Data

+1

Связанный код отсутствует «ReflectionHelper» –

+0

@AdamGreen Исправлено!Извини за это. –

1

Я использую классы отображения, но не-ум. Я решаю это вот так:

public class PersonMap : EntityTypeConfiguration<Person> 
{ 
    public PersonMap() 
    { 
     Map(m => { m.ToTable("Person"); m.MapInheritedProperties(); }); 

     HasKey(p => p.Id); 
     Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
    } 

} 

Помните - базовый класс должен быть абстрактным.

Смежные вопросы