Страницы

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

суббота, 15 февраля 2020 г.

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

#c_sharp #sql #entity_framework


Здравствуйте ,изучая материалы по Entity Framework прочитал о возможностях и разных
подходах при реализации наследования в данном фреймворке . И меня конечно же заинтересовал
такой вопрос : 

Какая разница между их использованием и реализацией при разработке?

Какие же пункты я буду освещать при формировании ответа  :


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


Значение данной статьи : пока изучал ,собственно, эти же подходы ,то в голову сразу
пришла идея создать пособие где каждый может наглядно увидеть разницу при их использовании
и на основе характеристик и практических примеров увидеть и решить для себя какой именно
подход будет оптимальным
    


Ответы

Ответ 1



Итак ,начнем пожалуй . Теоретическая часть : Всего в 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(Таблица на одну иерархию) для оптимизации работы запросов - будет оптимальней Надеюсь ,я этой статьей смог внести немного ясности в вопрос - если у вас есть пожелания ,то комментарии всегда открыты для вас - Спасибо за внимание,комрады! Само собой ссылки на источники : практические примеры и использование данных подходов в приложениях + отличительные характеристики каждого из подходов

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

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