LINQ генерирует SQL с дубликатами вложенных выборок - PullRequest
16 голосов
/ 21 января 2010

Я очень новичок в .NET Entity Framework и думаю, что это круто, но почему-то у меня возникает эта странная проблема (извините за испанский, но моя программа на этом языке, в любом случае, это не имеет большого значения, только имена столбцов или свойств): я делаю обычный запрос LINQ To Entities, чтобы получить список UltimaConsulta, например:

var query = from uc in bd.UltimasConsultas
            select uc;

UltimasConsultas - это представление, кстати. Дело в том, что LINQ генерирует этот SQL для запроса:

SELECT 
[Extent1].[IdPaciente] AS [IdPaciente], 
[Extent1].[Nombre] AS [Nombre], 
[Extent1].[PrimerApellido] AS [PrimerApellido], 
[Extent1].[SegundoApellido] AS [SegundoApellido], 
[Extent1].[Fecha] AS [Fecha]
FROM (SELECT 
      [UltimasConsultas].[IdPaciente] AS [IdPaciente], 
      [UltimasConsultas].[Nombre] AS [Nombre], 
      [UltimasConsultas].[PrimerApellido] AS [PrimerApellido], 
      [UltimasConsultas].[SegundoApellido] AS [SegundoApellido], 
      [UltimasConsultas].[Fecha] AS [Fecha]
      FROM [dbo].[UltimasConsultas] AS [UltimasConsultas]) AS [Extent1]

Почему LINQ генерирует вложенный Select? Из видео и примеров я подумал, что он генерирует обычные SQL-запросы для такого рода запросов. Нужно ли что-то настраивать (модель сущностей генерировалась из мастера, так что это конфигурация по умолчанию)? Заранее спасибо за ваши ответы.

Ответы [ 2 ]

13 голосов
/ 22 января 2010

Для ясности, LINQ to Entities не генерирует SQL. Вместо этого он генерирует дерево канонических команд ADO.NET, а поставщик ADO.NET для вашей базы данных, в данном случае, предположительно, SQL Server, генерирует SQL.

Так почему же он генерирует эту производную таблицу (я думаю, что «производная таблица» является более правильным термином для используемой здесь функции SQL)? Поскольку код, который генерирует SQL, должен генерировать SQL для широкого спектра запросов LINQ, большинство из которых не так тривиальны, как тот, который вы показываете. Эти запросы часто выбирают данные для нескольких типов (многие из которых могут быть анонимными, а не именованными типами), и для того, чтобы генерирование SQL было относительно нормальным, они группируются в экстенты для каждого типа.

Еще один вопрос: почему тебя это должно волновать? Легко продемонстрировать, что использование производной таблицы в этом выражении «свободно» с точки зрения производительности.

Я случайно выбрал таблицу из заполненной базы данных и выполнил следующий запрос:

SELECT [AddressId]
      ,[Address1]
      ,[Address2]
      ,[City]
      ,[State]
      ,[ZIP]
      ,[ZIPExtension]
  FROM [VertexRM].[dbo].[Address]

Давайте посмотрим на стоимость:

<StmtSimple StatementCompId="1" StatementEstRows="7900" StatementId="1" StatementOptmLevel="TRIVIAL" StatementSubTreeCost="0.123824" StatementText="/****** Script for SelectTopNRows command from SSMS  ******/&#xD;&#xA;SELECT [AddressId]&#xD;&#xA;      ,[Address1]&#xD;&#xA;      ,[Address2]&#xD;&#xA;      ,[City]&#xD;&#xA;      ,[State]&#xD;&#xA;      ,[ZIP]&#xD;&#xA;      ,[ZIPExtension]&#xD;&#xA;  FROM [VertexRM].[dbo].[Address]" StatementType="SELECT">
  <StatementSetOptions ANSI_NULLS="false" ANSI_PADDING="false" ANSI_WARNINGS="false" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="false" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="false" />
  <QueryPlan CachedPlanSize="9" CompileTime="0" CompileCPU="0" CompileMemory="64">
    <RelOp AvgRowSize="246" EstimateCPU="0.008847" EstimateIO="0.114977" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="7900" LogicalOp="Clustered Index Scan" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Scan" EstimatedTotalSubtreeCost="0.123824">

Теперь давайте сравним это с запросом с производной таблицей:

SELECT 
       [Extent1].[AddressId]
      ,[Extent1].[Address1]
      ,[Extent1].[Address2]
      ,[Extent1].[City]
      ,[Extent1].[State]
      ,[Extent1].[ZIP]
      ,[Extent1].[ZIPExtension]
  FROM (SELECT [AddressId]
          ,[Address1]
          ,[Address2]
          ,[City]
          ,[State]
          ,[ZIP]
          ,[ZIPExtension]
  FROM[VertexRM].[dbo].[Address]) AS [Extent1]

А стоимость:

<StmtSimple StatementCompId="1" StatementEstRows="7900" StatementId="1" StatementOptmLevel="TRIVIAL" StatementSubTreeCost="0.123824" StatementText="/****** Script for SelectTopNRows command from SSMS  ******/&#xD;&#xA;SELECT &#xD;&#xA;       [Extent1].[AddressId]&#xD;&#xA;      ,[Extent1].[Address1]&#xD;&#xA;      ,[Extent1].[Address2]&#xD;&#xA;      ,[Extent1].[City]&#xD;&#xA;      ,[Extent1].[State]&#xD;&#xA;      ,[Extent1].[ZIP]&#xD;&#xA;      ,[Extent1].[ZIPExtension]&#xD;&#xA;  FROM (SELECT [AddressId]&#xD;&#xA;          ,[Address1]&#xD;&#xA;          ,[Address2]&#xD;&#xA;          ,[City]&#xD;&#xA;          ,[State]&#xD;&#xA;          ,[ZIP]&#xD;&#xA;          ,[ZIPExtension]&#xD;&#xA;  FROM[VertexRM].[dbo].[Address]) AS [Extent1]" StatementType="SELECT">
  <StatementSetOptions ANSI_NULLS="false" ANSI_PADDING="false" ANSI_WARNINGS="false" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="false" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="false" />
  <QueryPlan CachedPlanSize="9" CompileTime="0" CompileCPU="0" CompileMemory="64">
    <RelOp AvgRowSize="246" EstimateCPU="0.008847" EstimateIO="0.114977" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="7900" LogicalOp="Clustered Index Scan" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Scan" EstimatedTotalSubtreeCost="0.123824">

В обоих случаях SQL Server просто сканирует кластерный индекс. Не удивительно, что стоимость почти точно такая же.

Давайте посмотрим на немного более сложный запрос. Я запустил LINQPad и ввел следующий запрос к той же таблице плюс одна связанная таблица:

from a in Addresses
select new
{
    Id = a.Id,
    Address1 = a.Address1,
    Address2 = a.Address2,
    City = a.City,
    State = a.State,
    ZIP = a.ZIP,
    ZIPExtension = a.ZIPExtension,
    PersonCount = a.EntityAddresses.Count()
}

Это генерирует следующий SQL:

SELECT 
1 AS [C1], 
[Project1].[AddressId] AS [AddressId], 
[Project1].[Address1] AS [Address1], 
[Project1].[Address2] AS [Address2], 
[Project1].[City] AS [City], 
[Project1].[State] AS [State], 
[Project1].[ZIP] AS [ZIP], 
[Project1].[ZIPExtension] AS [ZIPExtension], 
[Project1].[C1] AS [C2]
FROM ( SELECT 
    [Extent1].[AddressId] AS [AddressId], 
    [Extent1].[Address1] AS [Address1], 
    [Extent1].[Address2] AS [Address2], 
    [Extent1].[City] AS [City], 
    [Extent1].[State] AS [State], 
    [Extent1].[ZIP] AS [ZIP], 
    [Extent1].[ZIPExtension] AS [ZIPExtension], 
    (SELECT 
        COUNT(cast(1 as bit)) AS [A1]
        FROM [dbo].[EntityAddress] AS [Extent2]
        WHERE [Extent1].[AddressId] = [Extent2].[AddressId]) AS [C1]
    FROM [dbo].[Address] AS [Extent1]
)  AS [Project1]

Анализируя это, мы видим, что Project1 - это проекция на анонимный тип. Extent1 является Address таблицей / сущностью. И Extent2 - таблица для ассоциации. Теперь нет производной таблицы для Address, но есть одна для проекции.

Я не знаю, писали ли вы когда-нибудь систему генерации SQL, но это нелегко. Я считаю, что общая проблема доказательства того, что запрос LINQ to Entities и SQL-запрос эквивалентны, является NP-трудной, хотя некоторые конкретные случаи, очевидно, намного проще. SQL намеренно неполон по Тьюрингу, потому что его разработчики хотели, чтобы все запросы SQL выполнялись за ограниченное время. LINQ, не так.

Короче говоря, эту проблему очень трудно решить, и сочетание Entity Framework и его провайдеров иногда жертвует некоторой читабельностью в пользу согласованности по широкому кругу запросов. Но это не должно быть проблемой производительности.

0 голосов
/ 22 января 2010

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

Это то, что просит ваш запрос. Просто LINQ не может добавить подстановочный знак, как если бы вы сделали это вручную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...