实体框架生成可怜的sql(linqpad生成更好)

时间:2013-12-14 05:25:30

标签: c# linq wcf entity-framework odata

我有一个数据库结构如下

家庭(1)-----(*)FamilyPersons -----(1)人(1)------()费用(1)--- - (0..1)GroceriesDetails

让我解释这个关系,家庭可以有一个或多个人,我们有一个家庭和人之间的映射表FamilyPersons。现在每个人都可以输入费用进入费用表。费用表有一列ExpenseType(杂货,娱乐等) 每个这些费用的详细信息都放在他们自己的表中,所以我们有一个GroceriesDetails表(同样我们还有其他表),所以我们在Expense和Groceries之间有1到0..1的关系。

现在我正在编写一个查询来获取一个家庭的完整GroceriesDetails

GroceriesDetails.Where (g => g.Expenses.Person.FamilyPersons.Any(fp => 
fp.FamilyId == 1) && g.Expenses.ExpenseType == "GC" )

为此,EF生成的SQL是

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Amount] AS [Amount]
FROM  [dbo].[GroceriesDetails] AS [Extent1]
INNER JOIN  (SELECT [Extent3].[Id] AS [Id1]
    FROM  [dbo].[Expenses] AS [Extent2]
    INNER JOIN [dbo].[GroceriesDetails] AS [Extent3] ON [Extent2].[Id] = [Extent3].[Id]
    WHERE N'GC' = [Extent2].[ExpenseType] ) AS [Filter1] ON [Extent1].[Id] = [Filter1].[Id1]
WHERE  EXISTS (SELECT 
    1 AS [C1]
    FROM   [dbo].[Expenses] AS [Extent4]
    INNER JOIN [dbo].[GroceriesDetails] AS [Extent5] ON [Extent4].[Id] = [Extent5].[Id]
    INNER JOIN [dbo].[FamilyPerson] AS [Extent6] ON [Extent4].[PersonId] = [Extent6].[PersonId]
    WHERE ([Extent1].[Id] = [Extent5].[Id]) AND (1 = [Extent6].[FamilyId])
)

在此查询中,Expenses和GroceriesDetails表之间存在完整的表连接,这会导致性能问题。

Linqpad生成更好的SQL

SELECT [t0].[Id], [t0].[Amount]
FROM [GroceriesDetails] AS [t0]
INNER JOIN [Expenses] AS [t1] ON [t1].[Id] = [t0].[Id]
WHERE (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [Expenses] AS [t2]
    INNER JOIN [Person] AS [t3] ON [t3].[Id] = [t2].[PersonId]
    CROSS JOIN [FamilyPerson] AS [t4]
    WHERE ([t4].[FamilyId] = @p0) AND ([t2].[Id] = [t0].[Id]) AND ([t4].[PersonId] = 
[t3].[Id])
    )) AND ([t1].[ExpenseType] = @p1)

请注意,我们正在使用WCF数据服务,因此此查询是针对WCF数据服务引用编写的,因此我无法从顶部(系列)遍历到底部(Groceries),因为OData只允许一个级别的选择。

有关优化此代码的任何帮助表示赞赏。

1 个答案:

答案 0 :(得分:3)

从评论中我了解到,当应用程序使用EF时,LinqPad使用Linq2SQL,这解释了差异。

问题在于你无法控制EF如何生成SQL。 您唯一能做的就是重写LINQ查询,使其“更接近”所需的SQL。

例如,而不是

GroceriesDetails.Where (g => g.Expenses.Person.FamilyPersons.Any(fp => fp.FamilyId == 1)
                             && g.Expenses.ExpenseType == "GC" )

您可以尝试编写类似(伪代码)的内容:

from g in GrosseriesDetails
join e in Expenses on g.Id = e.GrosseryId
join p in Persons on p.Id = e.PersonId
join f in FamilyPersons on f.PersonId = p.Id
where f.FamilyId == 1 && e.ExpenseType == "GC"

它几乎总是有帮助,因为它告诉ORM将其转换为SQL的简单方法。这个想法是,“原始”案例中的表达式树与提议的场景相比更复杂,通过简化表达式树,我们使翻译人员的工作更容易,更直接。

但除了操纵LINQ之外,无法控制它如何从表达式树生成SQL。