Entity Framework/Linq to SQL: Skip & Take
Просто любопытно, как должны работать Skip и Take. Я получаю результаты, которые хочу увидеть на стороне клиента, но когда я подключаю AnjLab SQL Profiler и смотрю на SQL, который выполняется, он выглядит так, как будто он запрашивает и возвращает весь набор строк в клиент.
Действительно ли он возвращает все строки, сортируя и сужая материал с помощью LINQ на стороне клиента?
Я попытался сделать это как с Entity Framework, так и с Linq to SQL; оба имеют одинаковое поведение.
Не уверен, что это имеет значение, но я использую С# в VWD 2010.
Любое понимание?
public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
var context = new TectonicEntities();
totalRecords = context.Stores.Count();
int skipRows = (page - 1) * pageSize;
if (desc)
return context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
return context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
}
Результат SQL (Примечание: я исключаю запрос Count):
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[LegalName] AS [LegalName],
[Extent1].[YearEstablished] AS [YearEstablished],
[Extent1].[DiskPath] AS [DiskPath],
[Extent1].[URL] AS [URL],
[Extent1].[SecureURL] AS [SecureURL],
[Extent1].[UseSSL] AS [UseSSL]
FROM [dbo].[tec_Stores] AS [Extent1]
После некоторых дальнейших исследований я обнаружил, что следующее работает так, как я ожидал бы этого:
public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
var context = new TectonicEntities();
totalRecords = context.Stores.Count();
int skipRows = (page - 1) * pageSize;
var qry = from s in context.Stores orderby s.Name ascending select s;
return qry.Skip(skipRows).Take(pageSize);
}
Результат SQL:
SELECT TOP (3)
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[LegalName] AS [LegalName],
[Extent1].[YearEstablished] AS [YearEstablished],
[Extent1].[DiskPath] AS [DiskPath],
[Extent1].[URL] AS [URL],
[Extent1].[SecureURL] AS [SecureURL],
[Extent1].[UseSSL] AS [UseSSL]
FROM ( SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[LegalName] AS [LegalName], [Extent1].[YearEstablished] AS [YearEstablished], [Extent1].[DiskPath] AS [DiskPath], [Extent1].[URL] AS [URL], [Extent1].[SecureURL] AS [SecureURL], [Extent1].[UseSSL] AS [UseSSL], row_number() OVER (ORDER BY [Extent1].[Name] ASC) AS [row_number]
FROM [dbo].[tec_Stores] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 3
ORDER BY [Extent1].[Name] ASC
Мне очень нравится, как работает первый вариант; Передача в выражении лямбда для сортировки. Есть ли способ выполнить одно и то же в синтаксисе SQL-запроса LINQ to SQL? Я пробовал использовать qry.OrderBy(sort).Skip(skipRows).Take(pageSize), но это в итоге дало мне те же результаты, что и мой первый блок кода. Позволяет мне поверить, что мои проблемы каким-то образом связаны с OrderBy.
====================================
РЕШЕНИЕ ПРОБЛЕМЫ
Пришлось обернуть входящую лямбда-функцию в выражение:
Expression<Func<Store,string>> sort
Ответы
Ответ 1
Следующие работы и выполнение простоты, которую я искал:
public IEnumerable<Store> ListStores(Expression<Func<Store, string>> sort, bool desc, int page, int pageSize, out int totalRecords)
{
List<Store> stores = new List<Store>();
using (var context = new TectonicEntities())
{
totalRecords = context.Stores.Count();
int skipRows = (page - 1) * pageSize;
if (desc)
stores = context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
else
stores = context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
}
return stores;
}
Главное, что исправлено для меня, это изменить параметр сортировки Func на:
Expression<Func<Store, string>> sort
Ответ 2
Пока вы не делаете это, как queryable.ToList().Skip(5).Take(10)
, он не вернет весь набор записей.
Take
Выполнение только Take(10).ToList()
, делает SELECT TOP 10 * FROM
.
Пропустить
Пропуск работает немного иначе, потому что в TSQL нет функции "LIMIT". Однако он создает SQL-запрос, основанный на работе, описанной в этом сообщении ScottGu.
Если вы увидите, что весь набор записей возвращен, это, вероятно, связано с тем, что вы делаете ToList()
где-то слишком рано.
Ответ 3
Решение Entity Framework 6 здесь...
http://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/
например.
using System.Data.Entity;
....
int skip = 5;
int take = 10;
myQuery.Skip(() => skip).Take(() => take);
Ответ 4
Попробуйте следующее:
public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
var context = new TectonicEntities();
var results = context.Stores;
totalRecords = results.Count();
int skipRows = (page - 1) * pageSize;
if (desc)
results = results.OrderByDescending(sort);
return results.Skip(skipRows).Take(pageSize).ToList();
}
по правде говоря, что последний .ToList() на самом деле не нужен, поскольку вы возвращаете IEnumerable...
Будет выполнено два вызова базы данных: один для подсчета и один, когда выполняется ToList().
Ответ 5
Я создал простое расширение:
public static IEnumerable<T> SelectPage<T, T2>(this IEnumerable<T> list, Func<T, T2> sortFunc, bool isDescending, int index, int length)
{
List<T> result = null;
if (isDescending)
result = list.OrderByDescending(sortFunc).Skip(index).Take(length).ToList();
else
result = list.OrderBy(sortFunc).Skip(index).Take(length).ToList();
return result;
}
Простое использование:
using (var context = new TransportContext())
{
var drivers = (from x in context.Drivers where x.TransportId == trasnportId select x).SelectPage(x => x.Id, false, index, length).ToList();
}