4
        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 语句?


回复亚当·米尔斯:

1)我对你的回复有点困惑。在回复b)时,您同意如果说Linq2Entities Data Provider for SQL Server 支持特定的 Linq-to-Objects 运算符,那么它将尝试将其转换为等效的 SQL 语句,并且在回复c)时您还同意,如果这provider 支持特定的非 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 语句(而对于其他方法则抛出异常)?


第二次编辑:

好的,我按照你说的做了:

对于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();

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

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

先感谢您

4

2 回答 2

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()and numbers.FirstOrDefault()。这样做似乎效率极低,感觉就像一个错误。我想不出 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;
于 2012-11-07T18:27:23.073 回答
3

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

数据提供者有责任将表达式树转换为底层存储可以理解的格式,在您的情况下是 SQL,并且它将定义自己的规则。

有关 Linq-To-LDAP 提供程序的简单实现,请参见此处

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

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

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

 where c.ContactID == Test()

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

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

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

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

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

c) 正确,如将字符串 StartsWith 方法转换为 SQL 之类的

于 2012-11-09T19:45:39.123 回答