在Linq-to-Entities查询中嵌套Linq-to-Objects查询 - 在幕后发生了什么?

时间:2012-10-30 18:00:48

标签: linq-to-entities linq-to-objects

        var numbers = new int[] { 1, 2, 3, 4, 5 };

        var contacts = from c in context.Contacts
                       where c.ContactID == numbers.Max() | c.ContactID == numbers.FirstOrDefault()
                       select c;

        foreach (var item in contacts) Console.WriteLine(item.ContactID); ;

Linq-to-Entities查询首先被翻译成Linq 表达式树,然后由对象服务转换为命令树。如果Linq-to-Entities查询嵌套Linq-to-Objects查询,则此嵌套查询也会被转换为表达式树

a)我假设嵌套的Linq-to-Objects查询的操作符实际上都没有被执行,而是特定DB(或者对象服务)的数据提供程序知道如何转换逻辑Linq-to-Objects运算符到适当的SQL语句中?

b)数据提供程序知道如何仅为某些Linq-to-Objects运算符创建等效的SQL语句?

c)同样,数据提供程序知道如何仅为Net Framework类库中的某些非Linq方法创建等效的SQL语句?


回复ADAM MILLS:

1)我对你的回复感到有点困惑。在回复 b)时,您同意如果SQL Server的 Linq2Entities数据提供程序支持特定的Linq-to-Objects运算符,那么它会尝试将其转换为等效的SQL声明,并回复 c)您还同意,如果此提供程序支持特定的非Linq方法,它会将其转换为等效的SQL语句(如果它没有不支持它,它会引发异常)。但是对于 a),你回答的恰好与你对 c)所说的相反,因此该提供者不会尝试将Max转换为等效的Sql语句,但是会改为执行它并在查询中使用返回的值吗?

2)无论如何,我只知道一些Sql所以我不能完全确定,但是阅读为上面的代码生成的Sql查询似乎数据提供者实际上没有执行numbers.Max方法,而只是某种方式发现numbers.Max应返回最大值,然后继续在生成的Sql查询中包含对 TSQL的内置MAX函数的调用。它还将numbers数组保存的所有值放入Sql查询中。

 SELECT CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN '0X0X'
         ELSE '0X1X'
       END                      AS [C1],
       [Extent1].[ContactID]    AS [ContactID],
       [Extent1].[FirstName]    AS [FirstName],
       [Extent1].[LastName]     AS [LastName],
       [Extent1].[Title]        AS [Title],
       [Extent1].[AddDate]      AS [AddDate],
       [Extent1].[ModifiedDate] AS [ModifiedDate],
       [Extent1].[RowVersion]   AS [RowVersion],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[CustomerTypeID]
       END                      AS [C2],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[InitialDate]
       END                      AS [C3],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[PrimaryDesintation]
       END                      AS [C4],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[SecondaryDestination]
       END                      AS [C5],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[PrimaryActivity]
       END                      AS [C6],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[SecondaryActivity]
       END                      AS [C7],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[Notes]
       END                      AS [C8],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[RowVersion]
       END                      AS [C9],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[BirthDate]
       END                      AS [C10],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[HeightInches]
       END                      AS [C11],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[WeightPounds]
       END                      AS [C12],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[DietaryRestrictions]
       END                      AS [C13]
FROM   [dbo].[Contact] AS [Extent1]
       LEFT OUTER JOIN (SELECT [Extent2].[ContactID]            AS [ContactID],
                               [Extent2].[BirthDate]            AS [BirthDate],
                               [Extent2].[HeightInches]         AS [HeightInches],
                               [Extent2].[WeightPounds]         AS [WeightPounds],
                               [Extent2].[DietaryRestrictions]  AS [DietaryRestrictions],
                               [Extent3].[CustomerTypeID]       AS [CustomerTypeID],
                               [Extent3].[InitialDate]          AS [InitialDate],
                               [Extent3].[PrimaryDesintation]   AS [PrimaryDesintation],
                               [Extent3].[SecondaryDestination] AS [SecondaryDestination],
                               [Extent3].[PrimaryActivity]      AS [PrimaryActivity],
                               [Extent3].[SecondaryActivity]    AS [SecondaryActivity],
                               [Extent3].[Notes]                AS [Notes],
                               [Extent3].[RowVersion]           AS [RowVersion],
                               cast(1 as bit)                   AS [C1]
                        FROM   [dbo].[ContactPersonalInfo] AS [Extent2]
                               INNER JOIN [dbo].[Customers] AS [Extent3]
                                 ON [Extent2].[ContactID] = [Extent3].[ContactID]) AS [Project1]
         ON [Extent1].[ContactID] = [Project1].[ContactID]
       LEFT OUTER JOIN (SELECT TOP (1) [c].[C1] AS [C1]
                        FROM   (SELECT [UnionAll3].[C1] AS [C1]
                                FROM   (SELECT [UnionAll2].[C1] AS [C1]
                                        FROM   (SELECT [UnionAll1].[C1] AS [C1]
                                                FROM   (SELECT 1 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable1]
                                                        UNION ALL


                                                        SELECT 2 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable2]) AS [UnionAll1]
                                                UNION ALL


                                                SELECT 3 AS [C1]
                                                FROM   (SELECT 1 AS X) AS [SingleRowTable3]) AS [UnionAll2]
                                        UNION ALL


                                        SELECT 4 AS [C1]
                                        FROM   (SELECT 1 AS X) AS [SingleRowTable4]) AS [UnionAll3]
                                UNION ALL


                                SELECT 5 AS [C1]
                                FROM   (SELECT 1 AS X) AS [SingleRowTable5]) AS [c]) AS [Limit1]
         ON 1 = 1
       LEFT OUTER JOIN (SELECT TOP (1) [c].[C1] AS [C1]
                        FROM   (SELECT [UnionAll7].[C1] AS [C1]
                                FROM   (SELECT [UnionAll6].[C1] AS [C1]
                                        FROM   (SELECT [UnionAll5].[C1] AS [C1]
                                                FROM   (SELECT 1 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable6]
                                                        UNION ALL


                                                        SELECT 2 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable7]) AS [UnionAll5]
                                                UNION ALL


                                                SELECT 3 AS [C1]
                                                FROM   (SELECT 1 AS X) AS [SingleRowTable8]) AS [UnionAll6]
                                        UNION ALL


                                        SELECT 4 AS [C1]
                                        FROM   (SELECT 1 AS X) AS [SingleRowTable9]) AS [UnionAll7]
                                UNION ALL


                                SELECT 5 AS [C1]
                                FROM   (SELECT 1 AS X) AS [SingleRowTable10]) AS [c]) AS [Limit2]
         ON 1 = 1
       CROSS JOIN (SELECT MAX([UnionAll12].[C1]) AS [A1]
                   FROM   (SELECT [UnionAll11].[C1] AS [C1]
                           FROM   (SELECT [UnionAll10].[C1] AS [C1]
                                   FROM   (SELECT [UnionAll9].[C1] AS [C1]
                                           FROM   (SELECT 1 AS [C1]
                                                   FROM   (SELECT 1 AS X) AS [SingleRowTable11]
                                                   UNION ALL


                                                   SELECT 2 AS [C1]
                                                   FROM   (SELECT 1 AS X) AS [SingleRowTable12]) AS [UnionAll9]
                                           UNION ALL


                                           SELECT 3 AS [C1]
                                           FROM   (SELECT 1 AS X) AS [SingleRowTable13]) AS [UnionAll10]
                                   UNION ALL


                                   SELECT 4 AS [C1]
                                   FROM   (SELECT 1 AS X) AS [SingleRowTable14]) AS [UnionAll11]
                           UNION ALL


                           SELECT 5 AS [C1]
                           FROM   (SELECT 1 AS X) AS [SingleRowTable15]) AS [UnionAll12]) AS [GroupBy1]
WHERE  [Extent1].[ContactID] IN ([GroupBy1].[A1], (CASE
                                                     WHEN ([Limit1].[C1] IS NULL) THEN 0
                                                     ELSE [Limit2].[C1]
                                                   END))

基于此,Linq2Entities提供程序是否可能确实不执行非Linq和Linq-to-Object方法,而是为其中一些方法创建等效的SQL语句(对于其他方法,它会抛出异常)?< / p>


第二次编辑:

好的,我做了你告诉我的事情:

对于 b)我创建了Linq-to-Objects扩展方法:

public static class TEST_CLASS
{
    public static int Testing<TSource>(this IEnumerable<TSource> source)
    {
        Console.WriteLine("Testing Called"); // here I've put a breakpoint
        return source.Count();
    }
}

        List<int> list = new List<int>() {1,2,3,4,5,6 };

        var contact = (from c in context.Contacts
                       where c.ContactID == list.Testing()
                       select c).First();

当我在调试模式下运行代码时,我立即得到以下异常(因此调试器在抛出异常之前不会进入Testing方法):

  

System.NotSupportedException:LINQ to Entities无法识别   方法'Int32   TestingInt32'   方法,并且此方法无法转换为商店表达式。

对于 c)我创建了非Linq方法:

public class Another_TEST_CLASS
{
    public static int Testing_Again()
    {
        Console.WriteLine("Testing_Again called");// here I've put a breakpoint
        return 1000;
    }
}


        var contact = (from c in context.Contacts
                       where c.ContactID == Another_TEST_CLASS.Testing_Again()
                       select c).First();

当我在调试模式下运行代码时,我立即得到以下异常(因此调试器在抛出异常之前不会进入Testing_Again方法):

  

System.NotSupportedException:LINQ to Entities无法识别   方法'Int32 Testing_Again()'方法,这个方法不可能   翻译成商店表达。在   System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.Default

提前谢谢

2 个答案:

答案 0 :(得分:4)

我刚刚在LinqPad中尝试了这个,生成的SQL看起来像:

-- Region Parameters
DECLARE @p0 Int = 5
DECLARE @p1 Int = 1
-- EndRegion
SELECT [t0].[ContactID], [t0].[Name]
FROM [Contacts] AS [t0]
WHERE ([t0].[ContactID] = @p0) OR ([t0].[ContactID] = @p1)

这是使用Linq-to-Sql,但我认为Linq-to-Entities不会做任何不同的事情;提供程序将执行Linq-to-Objects查询并将结果插入表达式树。


修改

看起来Linq-to-Entities正在生成一个查询来评估服务器上的numbers.Max()numbers.FirstOrDefault()。这样做似乎非常低效,感觉就像一个bug。我无法想到L2E行为会优于L2S行为的任何情况。

您可以通过提取查询之外的相关值来强制执行L2S行为:

var numbers = new int[] { 1, 2, 3, 4, 5 };

int max = numbers.Max();
int first = numbers.FirstOrDefault();

var contacts = from c in context.Contacts
   where c.ContactID == max !| c.ContactID == first
   select c;

答案 1 :(得分:3)

编辑:我很抱歉,这个答案适用于Linq-To-SQL,它与Linq-To-Entities和不同的语义有不同的数据提供者。

数据提供程序负责将表达式树转换为底层商店理解的格式,在您的情况下为SQL,它将定义自己的规则。

See here for a simple implementation of a Linq-To-LDAP provider

对于Linq-Sql数据提供程序,它知道它需要一个where子句的值。基于表达式,它通过使用谓词参数(在您的示例c中)知道谓词的哪一部分来自查询表。等式表达式的另一面必须是作为参数传递的值或SQL(函数,查询)。

如果值表达式结果类型是已知的sql类型,并且它是从对象引用表达式(或对象引用上的方法调用)派生的,则表达式将被展开(执行),并且该值将作为一个值传递给查询参数。

如果表达式是直接方法引用,它将尝试将它与SQL函数匹配或抛出(即抛出)。

 where c.ContactID == Test()

如果表达式结果是IQueryable,它将继续将其转换为Sql。

a)在你的情况下,执行方法numbers.Max()IS并将返回的值用于查询,使用自定义扩展方法对其进行测试并在其中放入调试中断。

b)这是正确的,如果不使用本地声明的数字列表,则执行以下操作

cts = from c in context.Contacts
                   where c.ContactID == context.Numbers.Max()
                   select c;

它会将其转换为Numbers表的子查询(因为返回类型是IQueryable)。 如果您在这种情况下使用了提供程序不支持的方法,您将获得一个expcetion。再次测试这个使用自定义扩展方法。

c)正确,例如字符串StartsWith方法,转换为SQL,如