Подтвердить что ты не робот

LINQ генерирует SQL с дублированными вложенными выборками

Я очень новичок в платформе .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 генерирует вложенный выбор? Я подумал из видео и примеров, что он генерирует обычные SQL-запросы для такого рода запросов. Нужно ли мне что-то настраивать (модель объекта генерировалась из мастера, поэтому по умолчанию она была настроена)? Заранее благодарю за ваши ответы.

4b9b3361

Ответ 1

Чтобы быть ясным, 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-hard, хотя некоторые конкретные случаи, очевидно, намного проще. SQL преднамеренно Turing-неполный, потому что его дизайнеры хотели, чтобы все SQL-запросы выполнялись в ограниченное время. LINQ, не так.

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

Ответ 2

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

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