Оптимизируйте SQL, сгенерированный LINQ Query в Entity Framework 4.1, с однозначными связями - PullRequest
5 голосов
/ 27 июня 2011

У меня проблемы с запросом Sql, сгенерированным LINQ, Поскольку моя среда довольно большая, я сделал простой пример, который отражает мою проблему.

Это моя модель:

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; }
}

Очень просто, да?

Ну, это мой запрос:

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();

Проблема в том, что этот запрос переводится в SQL:

(from x in db.ClassAs
 where x.ID == 2
 select x).First()

становится:

SELECT TOP (1) 
[Extent1].[ID] AS [ID]
FROM [dbo].[ClassAs] AS [Extent1]
WHERE 2 = [Extent1].[ID]

и

from b in classA.Children
select b.Data).Skip(10).Take(10)

становится:

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)

Хотелось бы, чтобы сгенерированный запрос был примерно таким:

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

Большая проблема в том, что класс A -> класс B всегда имеет более 10 тыс. Строк, и так оно и есть, все эти строки загружаются в память, а подкачка выполняется в памяти, но мне бы хотелось, чтобы эта подкачка будет выполняться SQL Server.

Есть мысли о том, как этого добиться?

1 Ответ

2 голосов
/ 27 июня 2011

Вы должны различаться между linq-to-entity и linq-to-objects. Это:

var classA = (from x in db.ClassAs
              where x.ID == 2
              select x).First();

- это связь с сущностями. Вы получаете доступ к db.ClassAs, предоставляя IQueryable для построения дерева выражений, которое будет выполняться как SQL в базе данных при вызове First(). Но это:

var classesB = (from b in classA.Children
                select b.Data).Skip(10).Take(10);

- это linq-to-objects. Сам запрос определен для коллекции (HashSet) и выполнен в этой коллекции. Поскольку ваше свойство помечено как virtual EF запустит для вас отложенную загрузку и заполнит все данные в этом свойстве, чтобы вы могли выполнить их в запросе памяти. Это никогда не будет работать по-другому.

Если вы хотите сделать запрос к базе данных для вашего связанного свойства, вы должны использовать явную загрузку вместо отложенной загрузки:

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;
...