Comments 12
После Нового Года честно-честно прочитаю! ))
Спасибо Вам.
Спасибо Вам.
+6
Занимаюсь разработкой open-source проекта с использование Entity Framework уже несколько лет. И могу сказать, что для меня (как и для сообщества проекта) самым большим недостатком является отсутствие официальной поддержки second level caching
0
Любопытно. А если сравнивать не с Linq2Sql, а с NHibernate, то что можно сказать?
0
Если смотреть в перспективу, то 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 и т.п.)
Достаточно написать
То есть, всё дерево условий строится вручную, а не пишется интуитивно понятными выражениями.
Как видно из предыдущих примеров, 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). Из-за чего приходится покрывать код, где речь о вставке хотя бы тысячи объектов, костылями типа
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; }
+3
В последнем куске кода первая строка лишняя.
Кроме того, при выключении AutoDetectChangesEnabled, все изменённые объекты не сохраняются автоматически в БД при закрытии контекста (вероятно, требование NH описывать все поля в entity-классах, как virtual (даже простые int/string/date), не такое уж и глупое, т.к. позволяет легко отслеживать изменения в объектах)
Кроме того, при выключении AutoDetectChangesEnabled, все изменённые объекты не сохраняются автоматически в БД при закрытии контекста (вероятно, требование NH описывать все поля в entity-классах, как virtual (даже простые int/string/date), не такое уж и глупое, т.к. позволяет легко отслеживать изменения в объектах)
0
Из-за таких комментариев я люблю хабр, спасибо.
А что вы скажете насчет Linq to NHibernate?
Маленькая поправка: при закрытии контекста никогда ничего автоматически не сохраняется. Необходимо вызвать
Кроме того, есть возможность вручную вызывать детект изменений через
А что вы скажете насчет Linq to NHibernate?
при выключении AutoDetectChangesEnabled, все изменённые объекты не сохраняются автоматически в БД при закрытии контекста
Маленькая поправка: при закрытии контекста никогда ничего автоматически не сохраняется. Необходимо вызвать
SaveChanges()
. Кроме того, есть возможность вручную вызывать детект изменений через
context.ChangeTracker.DetectChanges()
. В зависимости от сложности модели, количестве объектов в контексте и характере операций, которые были выполнены, такой подход может поднять производительность. А может и значительно снизить.+1
>> Маленькая поправка: при закрытии контекста никогда ничего автоматически не сохраняется. Необходимо вызвать SaveChanges().
Совершенно верно. У нас аспектное управление тразнакциями (стартует, если впервые входит в метод, помеченный атрибутом, коммитится при выходе из метода или роллбэчится при исключении), поэтому совершенно забыл, как надо закрывать транзакции.
>> А что вы скажете насчет Linq to NHibernate?
В принципе работает, запускали простые примеры. Но дальше примеров не пошло, т.к. решили, что нужен полноценный ORM.
Совершенно верно. У нас аспектное управление тразнакциями (стартует, если впервые входит в метод, помеченный атрибутом, коммитится при выходе из метода или роллбэчится при исключении), поэтому совершенно забыл, как надо закрывать транзакции.
>> А что вы скажете насчет Linq to NHibernate?
В принципе работает, запускали простые примеры. Но дальше примеров не пошло, т.к. решили, что нужен полноценный ORM.
0
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. Зато столько возможностей…
+1
=>Как же хорошо, что существуют 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 вообще получается нефункциональная фича? Попробовал несколько разных запросов, на примере 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
0
Есть такой вопрос — а navigation property вообще получается нефункциональная фича?
Почему, очень даже полезная, если работа и правда происходит со всеми элементами коллекции. Т.е. когда загрузка в память желательна — она делается просто. Когда не желательна — да, надо искать другие средства.
var count = context.Threads.Where(e=> e.id == folder.id).Count();
Вы сравниваете Id потока с Id папки — это баг.
Ваш SQL работает для варианта 1 ко многим, а в задаче много ко многим.
0
Как то попутал со связями. Ну да ладно.
Все равно как то непонятно, почему две записи условно одинаковы, возвращают один и тот же набор данных, получают их с сервера одним и тем же запросом, но на первую можно навесить Count, First, или еще что, и все это войдет в запрос и будет выполняться на сервере, а во втором случае все довески уже будут работать в памяти на загруженной коллекции
context.SiteLink.Where(a => a.UserId == user.UserId)
user.SiteLink
Я правильно думаю, что неэквивалентность в том, что первый вариант дает «ленивую» коллекцию, которая материализуется при итерировании на ней, а второй вариант уже материализованную должен вернуть?
Все равно как то непонятно, почему две записи условно одинаковы, возвращают один и тот же набор данных, получают их с сервера одним и тем же запросом, но на первую можно навесить Count, First, или еще что, и все это войдет в запрос и будет выполняться на сервере, а во втором случае все довески уже будут работать в памяти на загруженной коллекции
context.SiteLink.Where(a => a.UserId == user.UserId)
user.SiteLink
Я правильно думаю, что неэквивалентность в том, что первый вариант дает «ленивую» коллекцию, которая материализуется при итерировании на ней, а второй вариант уже материализованную должен вернуть?
0
Sign up to leave a comment.
Еще один взгляд на Entity Framework: производительность и подводные камни