Как стать автором
Обновить

Комментарии 12

После Нового Года честно-честно прочитаю! ))
Спасибо Вам.
Занимаюсь разработкой open-source проекта с использование Entity Framework уже несколько лет. И могу сказать, что для меня (как и для сообщества проекта) самым большим недостатком является отсутствие официальной поддержки second level caching
Любопытно. А если сравнивать не с Linq2Sql, а с NHibernate, то что можно сказать?
Если смотреть в перспективу, то EF выглядит получше.
Microsoft имеет возможность модифицировать язык под фреймворк (ниже покажу на примере Expression Trees).

Прежде всего, в MS думают о юзабилити.
Если у db-класса Entity есть поле int EntityId, оно автоматически становится primary key, без дополнительных подсказок, если есть поле Order Order и поле int OrderId, фреймворк понимает, что OrderId — foreign Key для Order. В отличии от NH, EF не требует описания каждого поля как virtual, а только для lazy-полей.

Я считаю, MS не ввело bulk update именно из-за того, что не придумало обобщённый изящный интерфейс, подходящий одинаково как к SQL, так и к коллекциям в памяти. А лучше ещё подумать, чем выкатить костыль, который из-за совместимости останется надолго во фреймворке.

К слову, если в NH отмапить в одном entity одновременно и Order и OrderId, фреймворк будет падать где-то внутри с исключением «Индекс за пределами коллекции». Мне удалось выявить причину, только подключив исходники NH и подебажив их. Нужно одно из полей пометить как read-only (update=false, insert=false), чтобы можно было работать с таким маппингом.

Запросы в EF строятся через знакомый всем linq, а не с использованием новых классов и новых методов, как в NHibernate (всякие ICriterion, DetachedCriteria, Restrictions и т.п.)
Достаточно написать
context.Orders.Where(order => order.Dealer.Kind == 1 || order.Dealer.Kind == 2)
сравните с NH:
var qOrders = DetachedCriteria.For<Order>();
var qDealer = qOrders.CreateCriteria("Dealer");
qDealer.Add(Restriction.Or(Restriction.Eq("Kind", 1), Restriction.Eq("Kind", 2));
return session.List<Order>(qOrders);

То есть, всё дерево условий строится вручную, а не пишется интуитивно понятными выражениями.

Как видно из предыдущих примеров, NH ссылается на поля через их строковые имена, что противодействует автоматическому рефакторингу и поиску зависимостей. В инете описан велосипед указания полей через лямбду, но из-за reflection он небыстрый.

К слову, ради EF в c# 4.0 ввели expression trees. Например, когда я хочу вызвать функцию получения списка заказов с указанием ключа сортировки в качестве параметра, вызывающий код выглядит как GetMyOrders(order => order.Date). Но это не лямбда (т.е. не Func<Order,object>). Это Expression Tree (тип которого Expression<Func<Order, object>>), парсинг происходит не в runtime, выковыриванием ссылки на поле из кода геттера, а в compile-time.

Ещё о NH. Маппинги описываются в XML-аннотациях. Обычно, каждый entity-класс сопровождается xml-файлом с маппингом. Существует велосипед в community contrib делать маппинги атрибутами, который при запуске проекта рефлектит все классы в указанных сборках, делает XML из атрибутов и скармливает его ORM. Но не все конструкции, выражаемые в XML, можно описать атрибутами (хотя в одном проекте можно совмещать два подхода). В-общем, после NH переход на EF кажется очень приятным.

Плюсом NH отмечу глубочайшую интеграцию с log4net. Если включить максимальный уровень логирования, в десятках мегабайт чтива можно увидеть всё, что происходит под капотом при выполнении небольшой функции. Кроме того, можно включить только логгер SQL, чего я не нашёл в EF (да, в EF любой IQueryable при вызове ToString() обычно даёт SQL, но как одним движениям залогировать все SQL-и, отсылаемые в БД, я не знаю).

Из минусов EF отмечу катастрофичекое падение производительности при большом количестве объектов в контексте. Скорее всего, где-то зарылся небрежный алгоритм с O(N^2). Из-за чего приходится покрывать код, где речь о вставке хотя бы тысячи объектов, костылями типа
context.AutoDetectChangesEnabled = prevEnabledValue;
var prevEnabledValue = context.AutoDetectChangesEnabled;
try { 
    context.AutoDetectChangesEnabled = false;
    ... 
} finally { context.AutoDetectChangesEnabled = prevEnabledValue; }
В последнем куске кода первая строка лишняя.
Кроме того, при выключении AutoDetectChangesEnabled, все изменённые объекты не сохраняются автоматически в БД при закрытии контекста (вероятно, требование NH описывать все поля в entity-классах, как virtual (даже простые int/string/date), не такое уж и глупое, т.к. позволяет легко отслеживать изменения в объектах)
Из-за таких комментариев я люблю хабр, спасибо.

А что вы скажете насчет Linq to NHibernate?
при выключении AutoDetectChangesEnabled, все изменённые объекты не сохраняются автоматически в БД при закрытии контекста

Маленькая поправка: при закрытии контекста никогда ничего автоматически не сохраняется. Необходимо вызвать SaveChanges().
Кроме того, есть возможность вручную вызывать детект изменений через context.ChangeTracker.DetectChanges(). В зависимости от сложности модели, количестве объектов в контексте и характере операций, которые были выполнены, такой подход может поднять производительность. А может и значительно снизить.
>> Маленькая поправка: при закрытии контекста никогда ничего автоматически не сохраняется. Необходимо вызвать SaveChanges().

Совершенно верно. У нас аспектное управление тразнакциями (стартует, если впервые входит в метод, помеченный атрибутом, коммитится при выходе из метода или роллбэчится при исключении), поэтому совершенно забыл, как надо закрывать транзакции.

>> А что вы скажете насчет Linq to NHibernate?

В принципе работает, запускали простые примеры. Но дальше примеров не пошло, т.к. решили, что нужен полноценный ORM.
Microsoft имеет возможность модифицировать язык под фреймворк (ниже покажу на примере Expression Trees).

То, что Microsoft может вводить в C# для поддержки EF, можно использовать и в NHibernate.
Если у db-класса Entity есть поле int EntityId, оно автоматически становится primary key, без дополнительных подсказок, если есть поле Order Order и поле int OrderId, фреймворк понимает, что OrderId — foreign Key для Order.

«Автоматическое» определение чего-либо обычно уж очень подозрительно и чревато граблями. Указать поле для ключа совсем не долго.
В отличии от NH, EF не требует описания каждого поля как virtual, а только для lazy-полей.

Вроде он требует описывание как virtual не просто так? А для автоматического отслеживания изменений.
Запросы в EF строятся через знакомый всем linq, а не с использованием новых классов и новых методов, как в NHibernate (всякие ICriterion, DetachedCriteria, Restrictions и т. п.)

Возможность описывать запрос linq-выражением была ещё во второй версии отдельной библиотекой. В третьей она уже включена.
Ещё о NH. Маппинги описываются в XML-аннотациях. Обычно, каждый entity-класс сопровождается xml-файлом с маппингом. Существует велосипед в community contrib делать маппинги атрибутами, который при запуске проекта рефлектит все классы в указанных сборках, делает XML из атрибутов и скармливает его ORM. Но не все конструкции, выражаемые в XML, можно описать атрибутами (хотя в одном проекте можно совмещать два подхода). В-общем, после NH переход на EF кажется очень приятным.

В третьей версии NH появилась возможность описывать маппинги кодом. Причём возможности там, похоже, абсолютно все (я не сталкивался с тем, что что-то можно описать xml, но нельзя кодом).
Из минусов EF отмечу катастрофичекое падение производительности при большом количестве объектов в контексте.

Аналогично, была программа, где NH был почти в 3 раза быстрее. Как я ни бился, но разогнать EF не удалось.

Огромное преимущество и огромный недостаток NH по сравнению с EF в том, что он гораздо мощнее. И гораздо сложнее в освоении. Написать с нуля маппинг и сущности на NH почти на порядок дольше, чем на EF. Зато столько возможностей…
Справедливые замечания, мои впечатления относятся к версии NH 2.1.2.
=>Как же хорошо, что существуют navigation properties, думает он, пока не видит генерируемый SQL.
Есть такой вопрос — а navigation property вообще получается нефункциональная фича? Попробовал несколько разных запросов, на примере folder.Threads.Where(f=>…
Результат тот же самый, он полностью загружает коллекцию в память.
То есть использовать можно только вверх, от многих к одному?

И еще вопрос про методы обхода, почему вместо вот этого
=> var count = context.Threads.Count(f => f.Folders.Any(e => e.Id == folder.Id)));
не использовать вот так?
var count = context.Threads.Where(e=> e.id == folder.id).Count();

у меня получился более простой запрос, сущности не буду тут подменять, as is
SELECT
COUNT(1) AS [A1]
FROM [dbo].[SiteLink] AS [Extent1]
WHERE [Extent1].[UserId] = @p__linq__0
Есть такой вопрос — а navigation property вообще получается нефункциональная фича?

Почему, очень даже полезная, если работа и правда происходит со всеми элементами коллекции. Т.е. когда загрузка в память желательна — она делается просто. Когда не желательна — да, надо искать другие средства.

var count = context.Threads.Where(e=> e.id == folder.id).Count();

Вы сравниваете Id потока с Id папки — это баг.
Ваш SQL работает для варианта 1 ко многим, а в задаче много ко многим.
Как то попутал со связями. Ну да ладно.

Все равно как то непонятно, почему две записи условно одинаковы, возвращают один и тот же набор данных, получают их с сервера одним и тем же запросом, но на первую можно навесить Count, First, или еще что, и все это войдет в запрос и будет выполняться на сервере, а во втором случае все довески уже будут работать в памяти на загруженной коллекции
context.SiteLink.Where(a => a.UserId == user.UserId)
user.SiteLink

Я правильно думаю, что неэквивалентность в том, что первый вариант дает «ленивую» коллекцию, которая материализуется при итерировании на ней, а второй вариант уже материализованную должен вернуть?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории