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
提前谢谢
答案 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,如