使用一对多关联优化实体框架4.1中LINQ查询生成的SQL

Dav*_*son 5 c# linq sql-server query-optimization entity-framework-4.1

我对LINQ生成的Sql查询有一些问题,因为我的环境很大,我做了一个反映我的问题的简单例子.

这是我的模特:

public class ClassA
{
    public int ID { get; set; }
    public virtual ICollection<ClassB> Children { get; set; }
}

public class ClassB
{
    public int ID { get; set; }
    public string Data { get; set; }
}

public class ClassC
{
    public int ID { get; set; }
    public virtual ICollection<ClassB> Children { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

很简单吧?

嗯,这是我的查询:

var classA = (from x in db.ClassAs
             where x.ID == 2
             select x).First();
var classesB = (from b in classA.Children
                select b.Data).Skip(10).Take(10);

classesB.ToList();
Run Code Online (Sandbox Code Playgroud)

问题是当此查询转换为SQL时:

(from x in db.ClassAs
 where x.ID == 2
 select x).First()
Run Code Online (Sandbox Code Playgroud)

变为:

SELECT TOP (1) 
[Extent1].[ID] AS [ID]
FROM [dbo].[ClassAs] AS [Extent1]
WHERE 2 = [Extent1].[ID]
Run Code Online (Sandbox Code Playgroud)

和:

from b in classA.Children
select b.Data).Skip(10).Take(10)
Run Code Online (Sandbox Code Playgroud)

变为:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[Data] AS [Data], 
[Extent1].[ClassA_ID] AS [ClassA_ID]
FROM [dbo].[ClassBs] AS [Extent1]
WHERE ([Extent1].[ClassA_ID] IS NOT NULL) AND ([Extent1].[ClassA_ID] = @EntityKeyValue1)
Run Code Online (Sandbox Code Playgroud)

我希望生成的查询将是这样的:

SELECT [Data] AS [Data]
FROM (SELECT 
        [Data] AS [Data],
        rownum = ROW_NUMBER() OVER (ORDER BY [B].[ID])
        FROM ClassBs AS B , ClassAs AS A 
        WHERE B.ClassA_ID = A.ID
        AND A.ID = 2) AS T1
WHERE [t1].rownum BETWEEN 11 AND 20
ORDER BY [t1].rownum
Run Code Online (Sandbox Code Playgroud)

最大的问题是A类 - > B类总是超过10k行,就这样,所有这些行都被加载到内存中,并且分页是在内存中进行的,但我希望这个分页会由SQL Server完成.

有关如何实现这一点的任何想法?

Lad*_*nka 2

您必须区分 linq-to-entities 和 linq-to-objects。这:

var classA = (from x in db.ClassAs
              where x.ID == 2
              select x).First();
Run Code Online (Sandbox Code Playgroud)

是 linq-to-entities。您正在访问db.ClassAs提供IQueryable构建表达式树,当您调用时,该表达式树将作为 SQL 在数据库中执行First()。但是这个:

var classesB = (from b in classA.Children
                select b.Data).Skip(10).Take(10);
Run Code Online (Sandbox Code Playgroud)

是 linq 到对象。查询本身在集合 ( HashSet) 上定义并在该集合上执行。因为您的属性被标记为virtualEF,将为您触发延迟加载并填充该属性中的所有数据,以允许您在内存中执行查询。它永远不会以不同的方式工作。

如果要对相关属性进行数据库查询,则必须使用显式加载而不是延迟加载:

db.Entry(classA)
  .Collection(c => c.Children)
  .Query()
  .OrderBy(...) // You must order entities before you can use Skip and Take
  .Skip(10)
  .Take(10)
  .Load();

var classesB = classA.Children;
Run Code Online (Sandbox Code Playgroud)