Задача: заставить Linq-to-Entities генерировать достойный SQL без ненужных объединений

Недавно я столкнулся с вопросом на форуме Entity Framework на msdn: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/bb72fae4-0709-48f2-8f85-31d0b6a85f68

Человек, задавший вопрос, попытался выполнить относительно простой запрос, включающий две таблицы, группировку, сортировку по и агрегирование с использованием Linq-to-Entities. Довольно простой запрос Linq, который также легко выполнить в SQL - то, что люди пытаются делать каждый день.

Однако при использовании Linq-to-Entities результатом является сложный запрос с множеством ненужных объединений и т. Д. Я попробовал и не смог заставить Linq-to-Entities сгенерировать из него достойный SQL-запрос, если использовал только чистый Linq против сущностей EF.

Увидев изрядную долю запросов-монстров из EF, я подумал, что, возможно, OP (и я, и другие) что-то делают неправильно. Может есть способ лучше сделать?

Итак, вот моя задача: использовать пример из форума EF и использование только Linq-to-Entities для двух сущностей, возможно ли заставить EF сгенерировать запрос SQL без ненужных объединений и других сложностей?

Я хотел бы, чтобы EF генерировал что-то немного более близкое к тому, что Linq-to-SQL делает для того же типа запросов, при этом используя Linq против модели EF.

Ограничения: используйте EFv1 .net 3.5 SP1 или EFv4 (бета-версия 1 является частью бета-версии VS2010 / .net4, доступной для загрузки от Microsoft). Не разрешены трюки сопоставления CSDL-> SSDL, «определяющие запросы» модели, хранимые процессы, функции на стороне базы данных или представления. Просто простое сопоставление 1: 1 между моделью и базой данных и чистый запрос L2E, который выполняет то, что запрашивал исходный поток в MSDN. Между двумя объектами должна существовать связь (то есть мой "обходной путь №1" ответ исходному потоку не является допустимым обходным путем)

Обновление: добавлено вознаграждение в размере 500 пунктов. Развлекайся.

Обновление: как упоминалось выше, решение, использующее EFv4 / .net 4 (β1 или новее), конечно, имеет право на вознаграждение. Если вы используете .net 4 post β1, укажите номер сборки (например, 4.0.20605), используемый вами запрос L2E и SQL, который он сгенерировал и отправил в БД.

Обновление: эта проблема была исправлена ​​в VS2010 / .net 4 beta 2. Хотя сгенерированный SQL по-прежнему имеет несколько [относительно безобидных] дополнительных уровней вложенности, он не делает ничего лишнего. вещи, к которым он привык. Окончательный план выполнения после того, как оптимизатор SQL Server опробовал его, теперь настолько хорош, насколько это возможно. +++ для парней и парней, ответственных за часть EFv4, генерирующую SQL ...


person KristoferA    schedule 29.05.2009    source источник
comment
Я не понимаю, что вы пытаетесь показать. Я не решаюсь давать ответ, который можно было бы просто использовать, чтобы кого-то обрушить на технологию первой ревизии.   -  person Dan Blair    schedule 03.06.2009
comment
Я не пытаюсь ничего показывать, я хочу знать, возможно ли использование L2E / EF v1 (.net 3.5) или v2 (v4 / .net 4.0) для генерации SQL-запросов без всех избыточных дополнительных соединений и т. Д. I ' Я уверен, что должен быть способ, но я не могу этого сделать. Так что, чтобы удовлетворить свое любопытство, я назначил награду за ответ на относительно простой вопрос - надеюсь, это должно побудить кого-нибудь найти способ сделать это. Причина, по которой я выбрал конкретный пример, заключается в том, что человек, который первоначально задал вопрос, предоставил простую репликацию, используя очень простой запрос.   -  person KristoferA    schedule 03.06.2009
comment
... продолжение ... Если этот простой запрос можно исправить, то, вероятно, можно исправить и более сложные, используя [каков бы ни был ответ на этот вопрос] -технику / состав запроса.   -  person KristoferA    schedule 03.06.2009


Ответы (3)


Если бы меня так беспокоил сумасшедший SQL, я бы просто не выполнял группировку в базе данных. Сначала я бы запросил все данные, которые мне нужны, завершив их с помощью ToList (), используя функцию Include для загрузки всех данных за один выбор.

Вот мой окончательный результат:

var list = from o in _entities.orderT.Include("personT")
           .Where(p => p.personT.person_id == person_id && 
                       p.personT.created >= fromTime && 
                       p.personT.created <= toTime).ToList()
           group o by new { o.name, o.personT.created.Year, o.personT.created.Month, o.personT.created.Day } into g
           orderby g.Key.name
           select new { g.Key, count = g.Sum(x => x.price) };

Это приводит к гораздо более простому выбору:

SELECT 
1 AS [C1], 
[Extent1].[order_id] AS [order_id], 
[Extent1].[name] AS [name], 
[Extent1].[created] AS [created], 
[Extent1].[price] AS [price], 
[Extent4].[person_id] AS [person_id], 
[Extent4].[first_name] AS [first_name], 
[Extent4].[last_name] AS [last_name], 
[Extent4].[created] AS [created1]
FROM    [dbo].[orderT] AS [Extent1]
LEFT OUTER JOIN [dbo].[personT] AS [Extent2] ON [Extent1].[person_id] = [Extent2].[person_id]
INNER JOIN [dbo].[personT] AS [Extent3] ON [Extent1].[person_id] = [Extent3].[person_id]
LEFT OUTER JOIN [dbo].[personT] AS [Extent4] ON [Extent1].[person_id] = [Extent4].[person_id]
WHERE ([Extent1].[person_id] = @p__linq__1) AND ([Extent2].[created] >= @p__linq__2) AND ([Extent3].[created] <= @p__linq__3)

Кроме того, с предоставленными данными в качестве примера SQL Profiler замечает увеличение продолжительности вызова SQL только на 3 мс.

Лично я думаю, что любой, кто жалуется на то, что ему не нравится выходной SQL уровня ORM, должен вернуться к использованию хранимых процедур и наборов данных. Они просто еще не готовы к развитию, и им нужно провести еще несколько лет в пресловутой печи. :)

person Lusid    schedule 01.06.2009
comment
Тем не менее, он имеет на 2 соединения с таблицей людей больше, чем нужно. Задача состоит в том, чтобы от них избавиться. Это относительно простой пример, поэтому влияние на базу данных не так велико, как могло бы быть для более сложных примеров, но важно избавиться от лишних объединений, которые затрудняют оптимизатору SQL Server выбор правильного выполнения. план... - person KristoferA; 02.06.2009
comment
Усложняют ли дополнительные соединения оптимизатор SQL Server? Анализатор запросов должен уметь измерять влияние удаления двух соединений. - person Sean Reilly; 02.06.2009
comment
В таком очень простом запросе, как этот, разница между одним соединением и тремя соединениями, вероятно, мала с точки зрения оптимизатора, но я надеюсь найти ответ на этот вопрос, который затем может быть применен к более сложным запросам. В более реальном сценарии у меня могут быть запросы, требующие данных из 15 таблиц, и я надеюсь, что есть какой-то «волшебный трюк», который можно применить к запросам, которые сохраняют его до 15 таблиц, даже если DAL основан на L2E. + EF. - person KristoferA; 03.06.2009
comment
Не поймите меня неправильно. Полностью с вами согласен. Насколько я могу судить, стандартного способа решения этой проблемы не существует. Я видел многочисленные сообщения об этом как об ошибке на форумах MSDN, и большие парни постоянно говорят, что добавляют оптимизацию в следующий выпуск. Будем надеяться, что всякие XML-хаки для простых вещей не понадобятся и в следующем выпуске. Как и в случае с любой новой технологией, нам просто нужно подождать. - person Lusid; 03.06.2009
comment
Я думаю, вы правы, нам просто нужно подождать и посмотреть, исправят ли они это в 4.x ... В любом случае, идея награды заключалась в том, чтобы побудить людей искать обходной путь, и я надеюсь, что это имело такой эффект. Поскольку в вашем ответе показаны некоторые усилия по поиску обходного пути, я выберу его в качестве ответа - и выиграю награду. Спасибо за попытку. :) - person KristoferA; 06.06.2009
comment
Спасибо! Честно говоря, я бы хотел найти обходной путь. Я сам кое-чему научился в процессе, так что это того стоило. : D - person Lusid; 09.06.2009
comment
Я не понимаю, насколько это приемлемое решение. получение неагрегированных данных по сети и группировка в приложении? 2 избыточных соединения? - person Peter Radocchia; 09.08.2011

Интересное обсуждение. До сих пор я использовал 2 модели ORM (NHibernate и LINQ-to-Entities). По моему опыту, всегда есть линия, в которой вам нужно отказаться от ORM для создания SQL и вернуться к хранимым процедурам или представлениям для достижения наилучших масштабируемых запросов. Сказав это, я лично считаю, что LINQ лучше работает с более нормализованными базами данных, и все вложенные запросы / соединения не являются серьезной проблемой. В некоторых случаях для повышения производительности или масштабируемости вам необходимо использовать функции сервера БД (индексированные представления, например, в SQL 2008 SE работают только с подсказками запросов), и вы просто не можете использовать ORM (кроме iBatis?).

Конечно, вы не получите наилучшей производительности или масштабируемости, используя эти вложенные соединения / запросы, сгенерированные linq, но, пожалуйста, не забывайте о преимуществах и преимуществах разработки, предоставляемых LINQ (или NHibernate) в любом проекте. Несомненно, в этом должна быть какая-то заслуга.

Наконец, хотя я и рискую сравнивать яблоко и апельсины, но не думаю, что это больше похоже на вопрос: хотите ли вы быстрой разработки веб-сайта (веб-формы asp.net, Swing) или большего контроля над вашим HTML (asp.net mvc, RoR)? подберите то, что лучше всего соответствует вашим требованиям.

Мои 2 цента!

person Community    schedule 19.08.2009

SQL, который генерирует linq, очень эффективен. Это может показаться громоздким, но в нем учтены отношения в таблицах, ограничения и т. Д. На мой взгляд, вам следует просто слепо использовать команды linq и не беспокоиться о масштабе. У больших запросов есть преимущества, поскольку они генерируются автоматически. Он избегает любых ошибок в реляционных ограничениях и добавляет свои собственные оболочки для ошибок / исключений.

Однако, если вы хотите написать SQL самостоятельно и по-прежнему хотите работать в рамках ORM, попробуйте iBatis http://ibatis.apache.org/ Вы должны написать SQL и присоединиться сами, так что это дает вам полный контроль над бэкэнд-моделью.

Лично просто используйте SQLMetal и linq. Не беспокойтесь о производительности и масштабе, если вам это не нужно.

person Ritesh M Nayak    schedule 02.06.2009
comment
Вы абсолютно правы, что Linq-to-SQL генерирует эффективный SQL. См. Мой ответ на исходную ветку. Однако Linq-to-Entities - это совсем другая история, поэтому я поднял эту задачу и поэтому добавил к ней награду. Я заинтересован в том, чтобы L2E вел себя как L2S, и мне нужны обходные пути, которые заставят его это сделать ... - person KristoferA; 02.06.2009