【问题标题】:Recommend a fitting LINQ provider to me (SQL server, complex queries)向我推荐合适的 LINQ 提供程序(SQL 服务器、复杂查询)
【发布时间】: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 个查询!
  • 您可能会发现this 很有趣(尤其是关于 linq-to-sql 的部分)。另一种可能性是使用带有ToFuture 的NHibernate(即使它不是linq)。它是一种在一次往返中执行多个查询的功能。

标签: linq sql-server-2008 linq-to-sql c#-4.0 linq-to-entities


【解决方案1】:

一种解决方法是创建一个view

根据你的定义

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

并使用 linq-2-sql 查询该视图。

不确定我是否完全理解您的问题,但您可能会这样做

from x in MyContext.Sometable
Select new { x.a, x.b, x.t2.c, x.t2.d, x.t3.f } 

等等..我现在无法测试它,但我很确定你会创建你想要的选择(并且只有一个)。

【讨论】:

  • 创建视图并不是一个真正的选择,原因有两个:1)我想避免与 SQL 有任何关系,使用 LINQ 的目的是完全摆脱 SQL 并操纵对象图2)它会返回平面对象,而我需要一个图形,手动构建图形比我当前的代码更难维护(请记住,我们正在谈论许多嵌套级别,每个级别都有很多表,甚至地狱如果一切正常,我可以键入 LINQ 查询,因为它自然应该是查询很容易有 300 行长)
  • 由于大小限制,分成 2 个 cmets,我也不是在寻找解决方法,我当前的解决方法执行得很好,我只是想通过让它反映实际来使代码可维护对象图(而不是后来加入的大量平面数据),并且您提供的查询示例不起作用,正如您从导航属性上的选择中看到的那样,T2 和 T3 将是集合,因此无法执行 T2.c因为在 T2 上有很多带有“c”的项目,这让我们回到了嵌套选择和糟糕的代码生成
【解决方案2】:

我认为你能得到的最接近的是 NHibernate 的 Fetch(没有 linq)。

对于深度嵌套的数据(例如 ThenFetchMany),如果您也很快达到 NHibernate 的极限,我不会感到惊讶。在 O/RM 工具上,复杂的查询总是非常困难的。 NHibernate 在生成 SQL 方面一直给我留下深刻印象(linq-to-nhibernate 还没有!)。但即使在不太复杂的情况下,有时防止 1 + N 问题也是一项艰巨的工作。

也许通过 NHibernate 的HQL 你可以实现你想要的。

使用 linq,我认为您能做的最好的事情就是在尽可能少的查询中检索所需的对象图。

【讨论】:

  • 会在一段时间后接受这个作为答案,除非有人建议一个开箱即用的 linq 提供程序来满足我的需要。
【解决方案3】:

你也可以这样做:

var venderContacts= VendorContacts.ToLookup (u =>u.ContactID);
var contracts=Contacts
    .Select(cont=>new 
    {
        cont.EmailAddress,
        cont.EmailPromotion,
        Employees = cont.Employees
            .Select(emp=>new
            {
                emp.Gender,
                emp.HireDate
            }).ToList(),
        Vendors = venderContacts[cont.ContanctID]
    }).ToList();

【讨论】:

  • 我已经在做类似的事情了,正如我所说,我现在完全没有任何问题可以工作,我只是想避免这种混乱并拥有一个 linq 提供程序(即更改API,“不”更改我的代码)以便代码可以干净。一个体面的提供者应该能够在没有变通方法的情况下绘制图表
【解决方案4】:

我找到了一个似乎可以解决我的核心问题的提供程序(正常生成 SQL 与为子查询生成数百万条语句),但不确定它是否合适,因为这取决于他们的答案。

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=20658&StartAtMessage=0&#116494

我应该知道的任何其他供应商?如果我到现在为止都错过了这个,可能还有其他的,我很乐意比较它们。我现在拥有的是

完全不符合我对每行问题没有 1 个查询的要求: - linq 到 SQL - linq 到实体 - devart linqconnect

似乎有效 - llblgen

未经测试/需要反馈 - Telerik OpenAccess - NHibernate - Mindscape 光速

还有其他我应该知道的吗?

【讨论】:

  • 接受我自己的答案,因为我找到了这个,因为没有人建议工作提供者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 2014-01-06
  • 2018-02-08
  • 2019-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多