LINQ生成带有重复嵌套选择的SQL

Alf*_*uez 16 c# linq-to-entities entity-framework

我是.NET实体框架的新手,我认为这很棒,但不知怎的,我得到了这个奇怪的问题(抱歉西班牙语,但我的程序是用那种语言,无论如何这不是什么大问题,只是列或属性名称):我正在进行正常的LINQ To Entities查询以获取UltimaConsulta列表,如下所示:

var query = from uc in bd.UltimasConsultas
            select uc;
Run Code Online (Sandbox Code Playgroud)

UltmasConsultas是一个观点,顺便说一句.问题是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]
Run Code Online (Sandbox Code Playgroud)

为什么LINQ生成嵌套的Select?我从视频和示例中想到,它为这种查询生成了正常的SQL选择.我是否必须配置某些内容(实体模型是从向导生成的,因此它是默认配置)?提前感谢您的回答.

Cra*_*ntz 13

要清楚,LINQ to Entities不会生成SQL.相反,它会生成一个ADO.NET规范命令树,而数据库的ADO.NET提供程序(在这种情况下可能是SQL Server)会生成SQL.

那么为什么它会生成这个派生表(我认为"派生表"是这里使用的SQL特性的更正确的术语)?因为生成SQL的代码必须为各种LINQ查询生成SQL,所以大多数LINQ查询并不像您显示的那样简单.这些查询通常会选择多种类型的数据(其中许多可能是匿名的,而不是命名类型),并且为了使SQL生成相对合理,它们被分组为每种类型的范围.

另一个问题:你为什么要关心?从性能的角度来看,很容易证明在此语句中使用派生表是"免费的".

我从填充的数据库中随机选择了一个表,并运行以下查询:

SELECT [AddressId]
      ,[Address1]
      ,[Address2]
      ,[City]
      ,[State]
      ,[ZIP]
      ,[ZIPExtension]
  FROM [VertexRM].[dbo].[Address]
Run Code Online (Sandbox Code Playgroud)

我们来看看成本:

<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">
Run Code Online (Sandbox Code Playgroud)

现在让我们将它与派生表的查询进行比较:

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]
Run Code Online (Sandbox Code Playgroud)

而且费用:

<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">
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,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()
}
Run Code Online (Sandbox Code Playgroud)

这会生成以下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]
Run Code Online (Sandbox Code Playgroud)

分析这一点,我们可以看到这Project1是对匿名类型的投影.Extent1Address表/实体.并且Extent2是该协会的表格.现在没有派生表Address,但有一个用于投影.

我不知道你是否编写过SQL生成系统,但这并不容易.我认为证明LINQ to Entities查询和SQL查询等效的一般问题是NP难的,尽管某些特定情况显然要容易得多.SQL故意图灵不完整,因为它的设计者希望所有SQL查询在有限的时间内执行.LINQ,不是这样.

简而言之,这是一个非常难以解决的问题,实体框架及其提供者的组合有时会牺牲一些可读性,有利于在各种查询中保持一致性.但它不应该是性能问题.