Страницы

Поиск по вопросам

четверг, 25 апреля 2019 г.

Практическая разница между подходами к наследованию в Entity Framework при разработке

Здравствуйте ,изучая материалы по Entity Framework прочитал о возможностях и разных подходах при реализации наследования в данном фреймворке . И меня конечно же заинтересовал такой вопрос :
Какая разница между их использованием и реализацией при разработке?
Какие же пункты я буду освещать при формировании ответа :
Теория - здесь я буду рассказывать какие собственно есть подходы и как они отличаются в плане реализации Практика - здесь я буду показывать саму реализацию кода модели классов + покажу как будет выглядеть таблица/таблицы баз данных при разных подходах Характеристическое сравнение - в заключительном участке мы с вами выясним какой же подход ,в какой категории и почему именно этому подходу дано предпочтение .
Значение данной статьи : пока изучал ,собственно, эти же подходы ,то в голову сразу пришла идея создать пособие где каждый может наглядно увидеть разницу при их использовании и на основе характеристик и практических примеров увидеть и решить для себя какой именно подход будет оптимальным


Ответ

Итак ,начнем пожалуй .
Теоретическая часть :
Всего в Entity Framework есть всего 3 подхода :
TPH (Table Per Hierarchy - Таблица на одну иерархию классов) TPT (Table Per Type - Таблица на тип) TPC (Table Per Class(Concrete Type) - Таблица на каждый отдельный тип/класс)
1) TPH (Table Per Hierarchy - Таблица на одну иерархию классов) - При использовании данного подхода - для одной иерархии классов используется одна таблица.
Данные базовых и производных классов сохраняются в одну таблицу, а для их отличия создается специальный столбец.
2) TPT (Table Per Type - Таблица на тип) - данный подход предполагает сохранение в общей таблице только тех свойств, которые общие для всех классом-наследников, то есть которые определены в базовом классе.
А те свойства, которые относятся только к производному классу, сохраняются в отдельной таблице.
3) TPC (Table Per Class(Concrete Type) - Таблица на каждый отдельный тип/класс) - предполагает создание для каждой модели по отдельной таблицы. Столбцы в каждой таблице создаются по всем свойствам, в том числе и унаследованным.
Здесь мы с теоретической частью пожалуй закончим - перейдем до практики
Практическая часть:
Я считаю ,что надо выяснить одну деталь сразу :
Суть и предназначение у этих подходов одна - отобразить зависимость класса-наследника от класса-родителя и разность заключается в том ,что я буду показывать какой именно подход будет оптимальней с разных характеристических сторон(ну и само собою - отличия при их практической реализации). Спасибо за внимание!
1) Подход TPH :
Итак у нас есть базовая мини-иерархия двух классов :
public class Phone { public int Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } }
public class Smartphone : Phone { public string OS { get; set; } }
class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet Phones { get; set; } public DbSet Smarts { get; set; } }
Здесь класс Smartphone наследуется от Phone, определяя одно свойство в дополнение к унаследованным.
И при работе будет создана такая таблица:

Кроме всех свойств классов Phone и Smartphone здесь также появляется еще один столбец - Discriminator. Он имеет тип nvarchar и имеет длину в 128 символов. Данный столбец и будет определять относится строка к типу Phone или Smartphone. Работа в программе :
using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 });
Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges();
foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); }
Прошу вас обратить внимание на одну деталь : при выводе данных из Phones также будет идти вывод экземпляра Smartphone вместе с остальными экземплярами ,поскольку здесь SmartPhone и есть объектом Phone
2) Подход TPT :
Чтобы применить подход, возьмем из предыдущего примера систему классов и добавим к классу Smartphone атрибут [Table]
public class Phone { public int Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } } [Table("Smartphones")] public class Smartphone : Phone { public string OS { get; set; } } class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet Phones { get; set; } public DbSet Smarts { get; set; } }
Все остальное остается также, как и при подходе TPH. Но теперь база данных будет содержать следующие таблицы:

Таблица для смартфонов содержит только одно поле OS, а также ключ Id для связи с таблицей Phones
Применение моделей будет аналогично подходу TPH(пункт выше)
using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 });
Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges();
foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); }
3) Подход TPC :
Чтобы применить подход, изменим объявления моделей и контекст следующим образом:
public class Phone { [Key, DatabaseGenerated (DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } public string Name { get; set; } public string Company { get; set; } public int Price { get; set; } }
public class Smartphone : Phone { public string OS { get; set; } }
class MobileContext : DbContext { public MobileContext() : base("DefaultConnection") { } public DbSet Phones { get; set; } public DbSet Smarts { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .Map(m => { m.MapInheritedProperties(); m.ToTable("Phones"); }); modelBuilder.Entity().Map(m => { m.MapInheritedProperties(); m.ToTable("Smarts"); }); } }
Во-первых, обратите внимание, что у класса Phone в качестве типа ключа используется не int, а Guid. Это поможет нам избежать некоторых проблем с ключами. Хотя также можно было бы использовать int с ручной установкой Id при создании объекта.
Во-вторых, при настройке сопоставления моделей и таблиц у каждой модели вызывается метод MapInheritedProperties(), который указывает Entity Framework-у, что в таблицу для данной модели надо включить также наследуемые свойства, а не только те, которые определены непосредственно в этой модели.
При генерации базы данных у нас будут созданы две таблицы с полным набором столбцов:

Применение моделей:
using(MobileContext db = new MobileContext()) { db.Phones.Add(new Phone {Name = "Samsung Galaxy S5", Company = "Samsung", Price = 14000 }); db.Phones.Add(new Phone {Name = "Nokia Lumia 630", Company = "Nokia", Price = 8000 });
Smartphone s1 = new Smartphone { Name = "iPhone 6", Company = "Apple", Price = 32000, OS = "iOS" }; db.Smarts.Add(s1); db.SaveChanges();
foreach (Phone p in db.Phones) Console.WriteLine("{0} ({1}) - {2}", p.Name, p.Company, p.Price); Console.WriteLine(); foreach (Smartphone p in db.Smarts) Console.WriteLine("{0} ({1}, {2}) - {3}", p.Name, p.Company, p.Price, p.OS); }
Несмотря на то, что объект Smartphone никак не связан с таблицей Phones, при извлечении данных он также будет находится в наборе db.Phones, потому что наследование все равно будет действовать.
Переходим к третьей части - Характеристическое сравнение :
Вот тут уже действительно интересно: В зависимости от ваших требований и требований заказчика - здесь попросту нет 'лучшего' решения ... Но:
Движок Entity Framework хоть и поддерживает подход TPC , но для его адекватной работы нам нужно метод OnModelCreating() переопределять , дабы фреймворк понял, что мы подвязываем два класса между собою наследственными связями - чтобы можно было нормально работать с результатом запросов используя этот подход в приложении .
Значит ли это то ,что в большинстве случаев нам будет придеться использовать 2-а первых подхода - TPH и TPT ,чтобы не тратить время на изощрения с TPC и относительную скорость работы приложения при получении запросов?
Давайте выясним!
Итак ,здесь мы использовать формат "критерий - условный победитель - почему" :
Скорость исполнения(работы) - TPH -> Таблица на одну иерархию классов в общем имеет лучшую скорость хотя бы потому ,что на не надо делать запросы JOIN поскольку все данные в одной таблице . Такое решение становиться даже более очевидным ,когда у нас наследственная иерархия делается "шире" и "глубже" Гибкость - TPT -> Таблица на тип более гибкая потому,что решает проблему редактирования и обновлений колонок дочерней-таблицы при этом не изменяя родительскую таблицу. Эстетичность - TPT -> это уже чисто субъективное мнение ,но как по-мне ,то TPT выглядит для мене более объектно-ориентированным подходом. Использование памяти - TPT -> если у вас иерархия наследования имеет очень много различных типов , то использование TPT позволит использовать данные ,которые имеют много незаполненных полей , тем более ,если структура баз данных решает проблему множества пустых полей ,то эта проблема вряд ли скажется на производительности работы запросов.
Как мы с вами можем заметить ,если вы или ваш заказчик знает на какой критерий делать упор - то вы можете сделать очевидный выбор исходя из информации выше
Единственный нюанс состоит в том ,что в 90% случаев ставка идет на Производительность,поэтому в большинстве случаев использование TPH(Таблица на одну иерархию) для оптимизации работы запросов - будет оптимальней
Надеюсь ,я этой статьей смог внести немного ясности в вопрос - если у вас есть пожелания ,то комментарии всегда открыты для вас - Спасибо за внимание,комрады!
Само собой ссылки на источники : практические примеры и использование данных подходов в приложениях + отличительные характеристики каждого из подходов

Комментариев нет:

Отправить комментарий