【发布时间】:2012-02-08 10:39:51
【问题描述】:
我已经使用 LINQ to SQL & to entity 有一段时间了,总体上对它们非常满意。 但是,我知道它们的局限性,特别是其中一个对我来说是一个大问题。 当您以
的形式进行复杂的嵌套查询时MyContext.SomeTable
.Select(item=>new{
item.SomeProperty1,
item.SomeProperty2,
item.NavigationProperty1
.Select(nav1=> new {// retrieve some properties}), // This triggers a single query as long as don't have more than one subquery
item.NavigationProperty2
.Select(nav2=> new {// retrieve some properties}) // This triggers one query PER ROW in the original query
});
我测试过的提供程序是 LINQ TO SQL / LINQ TO 实体(更糟糕的是,devart LINQConnect 的情况更糟,并且在第一个导航属性上每行生成 1 个)
我现在得到的是生成的(伪代码):
select t1.a,t1.b,t2.c,t2.d from mytable as t1
join navproperty1table as t2
和 100 万条(如果第一组有 100 万条结果)这样的查询:
select t3.e,t3.f from navproperty2table as t3 where id = X(X 查询时 X 更改为第一个查询返回的下一个元素)
我想要什么:
select t1.a,t1.b,t2.c,t2.d,t3.e,t3.f from mytable as t1
join navproperty1table as t2
join navproperty2table as t3
当然,如果原始表中有 3 行,那将不是问题,但是我的表中有成千上万到数百万行“并且”我需要一个复杂得多的查询单选(我想一次得到一个复杂的图表)。想想 20 多个表,其中包含 3-6 级嵌套,每个表访问额外的 2-5 个表。
我的 SQL 服务器可以完美应对它,我也不关心带宽,它位于通过千兆连接链接的实例上,我无法以延迟方式获取该数据,我实际上“使用”了所有它立即,所以它不仅仅是懒惰。现在出于性能原因,我不得不将查询拆分为许多小查询并在 LINQ 上手动将它们连接到对象大小,这为维护它的人提供了一些非常讨厌的代码,但这是我拥有的唯一实际解决方案,所以总的来说包括所有小查询和最终加入,我在一个完全无法维护的单一方法中处理了 600 多行不可拆分的代码。
在我去评估所有以这种思维方式工作的所有 LINQ 提供程序之前,实际上是否有“任何”LINQ 提供程序的生产准备就绪,或者我是否最好编写自己的代码并将其商业化? (我很惊讶他们实际上并不是都这样工作,我看不到一个例子,你会更好地使用 foreach 案例和那些我尝试过声称摆脱 n +1 with loadwith,不要摆脱它,因为他们仍然进行 n+1 查询,但只是在一次调用中批量处理,当 1 是 10 000 然后 10 000 000 时,1 次往返和 n+1 查询不令人满意然后是 10 000 000 000)
- (请注意,我在推测究竟是什么触发了这个,但这不是问题,无论是什么触发这个“确切地”我肯定会在我当前的上下文中命中它)
PS:请注意,我在 Windows Server 2008 或更高版本以及 SQL Server 2008 或更高版本上运行 .NET 4.0 完整配置文件,不支持其他任何东西的提供程序都可以,我对迁移的要求为零、可移植性、较低的 .net 版本、较低的 sql server 支持等。如果需要,可以选择迁移到更新的版本。我也没有任何建模或高级功能的先决条件,数据库已经存在,我只想查询表,所以没有建模/视图/DML/存储过程/函数支持的东西很好,我唯一的要求是对复杂查询和对象图的合理 SQL 生成
编辑:为了澄清,这里是一个每个人都可以得到的数据库问题的实际示例,adventureworks
为每个联系人查询员工
Contacts
.Select(cont=>new
{
cont.EmailAddress,
cont.EmailPromotion,
Employees = cont.Employees
.Select(emp=>new
{
emp.Gender,
emp.HireDate
}).ToList()
}).ToList()
生成
SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], (
SELECT COUNT(*)
FROM [HumanResources].[Employee] AS [t2]
WHERE [t2].[ContactID] = [t0].[ContactID]
) AS [value]
FROM [Person].[Contact] AS [t0] 左外连接 [HumanResources].[Employee] AS [t1] ON [t1].[ContactID] = [t0].[ContactID] ORDER BY [t0].[ContactID], [t1].[EmployeeID]
现在只查询每个联系人的供应商 联系人 .选择(续=>新 { 续电子邮件地址, 续电子邮件促销, 供应商 = cont.VendorContacts.Select(vend=>new { vend.ContactTypeID, vend.ModifiedDate }).ToList() }).ToList()
还可以:
SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[ContactTypeID], [t1].[ModifiedDate], (
SELECT COUNT(*)
FROM [Purchasing].[VendorContact] AS [t2]
WHERE [t2].[ContactID] = [t0].[ContactID]
) AS [value]
FROM [Person].[Contact] AS [t0] 左外连接 [Purchasing].[VendorContact] AS [t1] ON [t1].[ContactID] = [t0].[ContactID] ORDER BY [t0].[ContactID], [t1].[VendorID]
现在同时查询两者(触发 X 行查询)
Contacts
.Select(cont=>new
{
cont.EmailAddress,
cont.EmailPromotion,
Employees = cont.Employees
.Select(emp=>new
{
emp.Gender,
emp.HireDate
}).ToList(),
Vendors = cont.VendorContacts.Select(vend=>new
{
vend.ContactTypeID,
vend.ModifiedDate
}).ToList()
}).ToList()
生成丑陋和缓慢的(出于明显的原因没有全部粘贴,但你明白了):
SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], (
SELECT COUNT(*)
FROM [HumanResources].[Employee] AS [t2]
WHERE [t2].[ContactID] = [t0].[ContactID]
) AS [value], [t0].[ContactID]
FROM [Person].[Contact] AS [t0]
LEFT OUTER JOIN [HumanResources].[Employee] AS [t1] ON [t1].[ContactID] = [t0].[ContactID]
ORDER BY [t0].[ContactID], [t1].[EmployeeID]
GO
-- Region Parameters
DECLARE @x1 Int = 1
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 2
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 3
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 4
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 5
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 6
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 7
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 8
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 9
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
-- Region Parameters
DECLARE @x1 Int = 10
-- EndRegion
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate]
FROM [Purchasing].[VendorContact] AS [t0]
WHERE [t0].[ContactID] = @x1
GO
我期望/希望看到的生成:
SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], [t2].[ContactTypeID], [t2].[ModifiedDate] ,[t0].[ContactID]
FROM [Person].[Contact] AS [t0]
LEFT OUTER JOIN [HumanResources].[Employee] AS [t1] ON [t1].[ContactID] = [t0].[ContactID]
LEFT OUTER JOIN [Purchasing].[VendorContact] AS [t2] ON [t2].[ContactID] = [t0].[ContactID]
GO
【问题讨论】:
-
它实际上是每行触发一个查询,还是生成一个相关的子查询,SQL Server 可能会针对某些连接式执行计划进行优化?
-
当最多有 1 个 nav 属性时,它会生成一个子查询(这很好),之后它会生成一个实际的查询 "per" row ,这很疯狂,如 select bla from t1 where id = 一些实际数字不是条件,这条线重复了 100 万次
-
我猜您正在寻找一种解决方案,该解决方案可以为导航属性上的第一个
Select()生成一个查询,然后为每个后续Select()生成一个查询(带有 WHERE 子句父母身份证)?我认为您不希望使用不可避免的笛卡尔积进行大型查询。 -
两者都不是,我最好寻找一个大的单个查询(但任何明智的都可以),它不会是一个完整的笛卡尔,而是简单地加入,因为导航属性包含所需的 FK 信息。我的帖子的底部示例我希望 linq 从联系人中检索选择任何内容加入 vendorcontacts 加入员工,一个查询,很多信息,并且提供者通过生成我在选择中要求的形状的输出图来管理它自己的工作,只是需要明确的是,父 ID 上的 where 子句是不可行的,因为有成千上万的父母,所以有 10 000 个查询!
标签: linq sql-server-2008 linq-to-sql c#-4.0 linq-to-entities