Skip and Take: эффективный подход к ограничению OFFSET в EF 4.1?
Следующий код:
using (var db = new Entities())
{
db.Blogs.First().Posts.Skip(10).Take(5).ToList();
}
Будет создан следующий SQL:
-- statement #1
SELECT TOP ( 1 ) [c].[Id] AS [Id],
[c].[Title] AS [Title],
[c].[Subtitle] AS [Subtitle],
[c].[AllowsComments] AS [AllowsComments],
[c].[CreatedAt] AS [CreatedAt]
FROM [dbo].[Blogs] AS [c]
-- statement #2
SELECT [Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[Text] AS [Text],
[Extent1].[PostedAt] AS [PostedAt],
[Extent1].[BlogId] AS [BlogId],
[Extent1].[UserId] AS [UserId]
FROM [dbo].[Posts] AS [Extent1]
WHERE [Extent1].[BlogId] = 1 /* @EntityKeyValue1 */
(из http://ayende.com/blog/4351/nhibernate-vs-entity-framework-4-0)
NB. Skip и Take не были переведены на SQL, в результате чего все сообщения из блога загружаются из базы данных, а не только 5, которые нам требуются.
Это кажется опасным, ужасно неэффективным. Невероятно, что дает?
Ответы
Ответ 1
Причина, по которой это происходит, - вызов First, из-за чего объект Blog
будет реализован. Для любого последующего обхода требуется больше запросов.
Попробуйте db.Blogs.Take(1).SelectMany(b => b.Posts).Skip(10).Take(5).ToList();
вместо этого сделать это в одном запросе. Вероятно, вы захотите добавить некоторый порядок упорядочения блогов до .Take(1)
, чтобы обеспечить детерминированный результат.
Edit
Фактически вам нужно использовать OrderBy перед пропуском (иначе LINQ to Entities будет генерировать исключение), что делает что-то вроде:
db.Blogs.OrderBy(b => b.Id).Take(1) // Filter to a single blog (while remaining IQueryable)
.SelectMany(b => b.Posts) // Select the blog posts
.OrderBy(p => p.PublishedDate).Skip(10).Take(5).ToList(); // Filter to the correct page of posts
Ответ 2
Как он предлагает в своем посте, вы можете использовать EQL для выполнения этого запроса. Что-то вроде:
// Create a query that takes two parameters.
string queryString =
@"SELECT VALUE product FROM
AdventureWorksEntities.Products AS product
order by product.ListPrice SKIP @skip LIMIT @limit";
ObjectQuery<Product> productQuery =
new ObjectQuery<Product>(queryString, context);
// Add parameters to the collection.
productQuery.Parameters.Add(new ObjectParameter("skip", 3));
productQuery.Parameters.Add(new ObjectParameter("limit", 5));
// Iterate through the collection of Contact items.
foreach (Product result in productQuery)
Console.WriteLine("ID: {0}; Name: {1}",
result.ProductID, result.Name);
Код, взятый отсюда: http://msdn.microsoft.com/en-us/library/bb738702.aspx
Ответ 3
Вы можете попытаться получить свой первый блог и использовать идентификатор блога для фильтрации сообщений, подобных этому:
Blog blog = db.Blogs.First();
blog.posts = Posts.Where(r=>r.blogID=blog.id).Skip(10).Take(5).ToList();