Не так давно я услышал мнение, что "Если надолго, надежно и с поддержкой - это Entity Framework. Если по-быстрому накидать что-нибудь простое - то ADO.NET". Времени обсуждать, почему человек так считает, не было, но его точка зрения мне запомнилась, потому что я это видел несколько иначе.
Эрик Эванс в своей книге "Предметно-ориентированное проектирование" упоминал, что разработчики нередко думают об объектах приложения в терминах таблиц баз данных. Вот у нас есть условный "Заказ", стало быть есть и таблица заказ, есть "Поставщик", значит и таблица такая. В итоге модель приложения ("домен") сливается со структурой базы данных и если вдруг приходит идея\необходимость разместить данные как-то иначе, это напрямую затрагивает модель, что как минимум нежелательно, а строго говоря - плохо.
Ведь тот самый условный объект "Заказ", который мы создаем в приложении и который в нем является целостным, может быть удобно "размазать" по нескольким таблицам БД, а потом, когда он снова понадобится, восстановить из этих нескольких таблиц.
Пример: класс Заказ (Order) содержит идентификатор, дату создания, еще что-нибудь (неважно) и компанию-получателя. Компания-получатель (Client) это тоже отдельный класс с идентификатором, названием и какими-нибудь реквизитами. Соответственно, в БД это будет скорее всего храниться так: таблица "Заказ" и таблица "Компания". В таблице "Заказ" в поле "Получатель" будет идентификатор компании из таблицы "Компания". В приложении же куда логичнее было бы, чтобы в объекте Order было поле типа Client, в котором лежит объект компании-получателя, а не условный int с идентификатором.
Так вот этот подход "Единый программный объект <--> Представление-в-БД-такое-какое-сейчас-удобнее", насколько мне видится, легко реализуется через обычное ADO.NET. Мы просто пишем слой доступа к данным, в котором классическим SQL выбираем нужные данные из скольких угодно таблиц, восстанавливаем объект Client и Order, вкладывая первый во второй, и все - готово. Просто и наглядно. Модель остается изолированной, а как она будет храниться - можно хоть сто раз переделать прозрачным для нее образом.
В случае же с EF я не вижу как такое можно сделать. Все примеры, которые я встречал в интернете демонстрируют подход, когда объект EF - один в один таблица БД. То есть получается объект Order с тем самым пресловутым int в качестве компании-получателя.
Что в итоге получается, что как раз ADO - это для "серьезно и надолго", а EF - "для простых случаев, чтобы не возиться с запросами, когда объект приложения - один в один таблица БД"? Или где я что-то пропустил или неправильно понял?
EF вам дает возможность автоматически транслировать экспрешны в диалекты SQL и маппить это на удобные дотнетные сущности. С голым ADO.NET вы будете делать то же самое, но руками.
куда логичнее было бы, чтобы в объекте Order было поле типа Client, в котором лежит объект компании-получателя, а не условный int с идентификатором.
Предлагаемая вами схема денормализована, вообще говоря, это не очень хорошо. Но посмотрите на Owned Types для EF Core и на Complex Types для EF6, мне кажется, что это то, что вы хотели бы - "встраивать" один объект в другой, сохраняя всё в единой таблице.
Да, EF не то, чтобы позволяет сильно отходить от классической реляционной модели, но с ним можно точечно звать raw sql-queries, что даст больше гибкости там, где это необходимо.
В БД она не денормализована, потому что как раз "Заказ" хранит число-идентификатор клиента, а сами клиенты лежат в отдельной таблице. А что касается объектов, когда они уже загружены в RAM, то там понятие нормализации звучит странно. Да и вообще насколько мне известно, нормализация в РЭУБД - скорее тема из университетов, а на деле бывает выгоднее даже провести денормализацию, чтобы данные считывались быстрее.
Статью посмотрел, но там смутило
Owned entities are essentially a part of the owner and cannot exist without it, they are conceptually similar to aggregates.
Как раз-таки в моем примере клиент - весьма самостоятельная сущность, которая может существовать сама по себе. Но надо подробнее ознакомиться будет с тем что там написано.
на деле бывает выгоднее даже провести денормализацию, чтобы данные считывались быстрее.
Вы правы, но для этого должны быть весомые основания - выигрыш перфоманса должен быть дороже перерасхода памяти. В противном случае частых таких выборов, я бы задумался насчет переезда на NoSQL или чего-то гибридного, как PostgreSQL. РСУБД - это про таблицы и отношения между ними все же.
в моем примере клиент - весьма самостоятельная сущность, которая может существовать сама по себе.
В тексте цитаты подразумевается то, что зависимая сущность не будет маппиться на отдельную таблицу (т.е. всегда будет пренадлежать родителю). В шарпах это будет отдельный класс, вложенный во "владеющий" тип.
Если по простому то так. Всю грязную работу сделает за вас EF.
Тут присутствуют 3 таблицы, контроль целостности и индексы для поиска.
Что же до ADO или там Daper честно голова болит портянки sql править.
Правда это не отменяет возможности пнуть приложение в дальнейшем, создав высокоскоростной адаптер для какой нибудь сущности.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HeIsBad.Dbl.Models
{
/// <summary>
/// Жалоба
/// </summary>
[Table("blames")]
public class DbBlame
{
/// <summary>
/// Идентификатор
/// </summary>
[Key]
public long Id { get; set; }
/// <summary>
/// Жалоба
/// </summary>
[Required]
public string Body { get; set; }
/// <summary>
/// Дата создания
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// Электронная почта жалобщика
/// </summary>
[Required]
public string Email { get; set; }
/// <summary>
/// Причина жалобы
/// </summary>
[Required]
[ForeignKey ("Reason")]
public int ReasonId { get; set; }
public virtual DbBlameReason Reason { get; set; }
/// <summary>
/// Адрес жалобы
/// </summary>
public string BlameUri { get; set; }
/// <summary>
/// Кем создано.
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// Статус жалобы
/// </summary>
[ForeignKey ("BlameStatus")]
public int StatusId { get; set; }
public virtual BlameStatus BlameStatus { get; set; }
}
}
Правильно ли я понял, что если я захочу сделать как я написал - собрать единый объект модели из данных, лежащих в разных таблицах, то в случае использования EF мне все равно придется иметь дело с объектами-таблицами, а из них уже вытаскивать данные и формировать объект модели?
Евгений, Не совсем обратите внимание на поле Reason в предыдущем примере.
Оно мэпится на нужную сущность, просто в нужный момент нужно описать это поведение, что бы не было лишних запросов.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HeIsBad.Dbl.Models
{
/// <summary>
/// Причина жалобы
/// </summary>
[Table("blamereasons")]
public class DbBlameReason
{
/// <summary>
/// Идентификатор
/// </summary>
[Key]
public int Id { get; set; }
/// <summary>
/// Имя
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// Описание
/// </summary>
public string Description { get; set; }
}
}
Ссылку посмотрел, но требуется больше времени, чтобы попробовать\осознать. Главный вывод, который я сделал - EF тоже позволяет сделать, то что я описывал, просто надо научиться настраивать. Может какие-нибудь еще ссылки\ресурсы\книги посоветуете, где бы нормально про EF писалось?
Эрик Эванс в своей книге "Предметно-ориентированное проектирование" упоминал, что разработчики нередко думают об объектах приложения в терминах таблиц баз данных. Вот у нас есть условный "Заказ", стало быть есть и таблица заказ, есть "Поставщик", значит и таблица такая. В итоге модель приложения ("домен") сливается со структурой базы данных и если вдруг приходит идея\необходимость разместить данные как-то иначе, это напрямую затрагивает модель, что как минимум нежелательно, а строго говоря - плохо.
Да, Эванс писал что-то подобное, но основной акцент у него был сделан на том, что связи, которые существуют между таблицами в БД, разработчики переносят их в модель, тем самым все усложняя.
И, приведенный вами пример, как раз это очень хорошо демонстрирует! Не нужна вам объектная ссылка между Client и Order, т.к. это разные агрегаты и каждый из них реализует свои инварианты. А вот ссылка между агрегатами в виде идентификаторов - int, Guid или чего-то другого, как раз является рекомендуемым способом.