实体框架/Linq to SQL:跳过和采取

Posted

技术标签:

【中文标题】实体框架/Linq to SQL:跳过和采取【英文标题】:Entity Framework/Linq to SQL: Skip & Take 【发布时间】:2011-04-21 16:04:32 【问题描述】:

只是好奇 Skip & Take 应该如何工作。我得到了我想在客户端看到的结果,但是当我连接 AnjLab SQL Profiler 并查看正在执行的 SQL 时,它看起来好像正在查询并将整组行返回到客户。

真的是返回所有行,然后在客户端使用 LINQ 对内容进行排序和缩小范围吗?

我已经尝试使用 Entity Framework 和 Linq to SQL 来做这件事;两者似乎具有相同的行为。

不确定它有什么不同,但我在 VWD 2010 中使用 C#。

有什么见解吗?

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

我真的很喜欢第一个选项的工作方式;传入一个 lambda 表达式进行排序。有没有办法在 LINQ to SQL orderby 语法中完成同样的事情?我尝试使用 qry.OrderBy(sort).Skip(skipRows).Take(pageSize),但最终得到的结果与我的第一个代码块相同。让我相信我的问题在某种程度上与 OrderBy 相关。

======================================

问题已解决

必须将传入的 lambda 函数包装在 Expression 中:

Expression<Func<Store,string>> sort

【问题讨论】:

你能给我们排序函数的代码吗? 当然,我只是传递了一个 lambda。示例:x => x.Name, x => x.LegalName, x => x.YearEstablished.ToString() 开始认为我应该只传递一个字符串,然后使用 switch 语句为 LINQ 查询设置适当的 orderby 参数 :( 第一种方法更酷,代码更少。我可以'不明白为什么它不能正常工作。在不知道到底发生了什么的情况下,似乎 .OrderBy 和 .OrderByDescending 正在触发数据库获取,然后应用排序,然后跳过并获取。也许就是这样……也许OrderBy 不知道如何将 x => x.Name 转换为适当的 SQL,因此它获取结果集然后应用排序和过滤。 【参考方案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

【讨论】:

我遇到了类似的问题。我在 SQL Profiler 中发现查询只是 SELECT * FROM 的,并追溯到调用中可能缺少这个 Expression 参数的事实。我更新了它(我使用 EF4.1 作为参考),它解决了我的问题。 有人知道为什么会这样吗?我有完全相同的问题。我以为我将整个表达式保留为 IQueryable 而不调用 ToList 或任何其他枚举表达式的东西。 Func 会导致枚举,而 Expression 不会? @BrianSweeney 我相信这样做的原因是,如果排序查询没有包含在表达式中,那么 linq 无法按顺序为其构建表达式树把它变成sql。正因为如此,它必须在执行跳过/采取之前枚举查询以对其进行排序 使其通用; code public IEnumerable ListEntities(Expression> sort, bool desc, int page, int pageSize, out int totalRecords) totalRecords = _dbSet.Count(); int skipRows = (page - 1) * pageSize; if (desc) return _dbSet.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList(); return _dbSet.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList(); code @BrianSweeney 和 @Anduril :问题在于,不是调用返回 IQueryableQueryable.OrderBy,而是调用仅返回 IEnumerableEnumerable.OrderBy。当然,从那时起,一切都会变得更糟。我认为当您的调用链从 IQueryable 更改为 IEnumerable 而没有明确使用 .AsEnumerable() 时,R# 会针对这种情况发出警告。【参考方案2】:

只要你不像queryable.ToList().Skip(5).Take(10)那样做,它就不会返回整个记录集。

拍摄

只做Take(10).ToList(),做SELECT TOP 10 * FROM

跳过

Skip 的工作方式有点不同,因为 TSQL 中没有“LIMIT”函数。但是,它会创建一个基于此 ScottGu blog post 中描述的工作的 SQL 查询。

如果您看到返回的整个记录​​集,可能是因为您在某处过早地执行ToList()

【讨论】:

最后执行 ToList()。示例:db.Stores.OrderBy(x => x.Name).Skip(5).Take(5).ToList() 是的,但更早。是否有其他方法在您的原始集合上执行 ToList()。 上面的代码不会返回你所有的数据。您要么必须更好地查看分析器,要么在代码中的其他位置对这些数据执行ToList。仅在执行这段代码时尝试运行分析器。 我已经使用 SQL Profiler 很长时间了,这个函数是唯一在程序执行期间触发的函数(正在查看底层 SQL 以决定实体框架与 nhibernate)。我尝试了另一种方法,并看到了我期望的 SQL(上面的代码和 SQL)。但是,我真的很喜欢能够传递一个 lambda 表达式进行排序,而不是必须在 linq 查询中设置它。 我不确定你的答案。至少在数据传输方面,即使我不使用 .ToList(),我也可以看到在 .take.skip 之前从数据库返回的所有数据上下文。它可能会消耗更多的 Kb【参考方案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);

【讨论】:

我认为您误解了这个问题。 OP 从未提及参数化SkipTake,只是它们没有在SQL 中执行(由于在OrderBy 中错误地使用了Func&lt;,&gt; 而不是Expression&lt;Func&lt;,&gt;&gt;)。这可能就是您将我的问题标记为重复的原因,即使它们不一样。【参考方案4】:

我创建了简单的扩展:

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

【讨论】:

【参考方案5】:

如果您使用 SQL Server 作为 DB

然后就可以转换了

context.Users.OrderBy(u => u.Id)
.Skip(() => 10)
.Take(() => 5)
.ToList

=>

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[UserName] AS [UserName]
FROM [dbo].[AspNetUsers] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY

参考:https://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/

【讨论】:

【参考方案6】:

试试这个:

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

将有 2 个数据库调用,一个用于计数,一个用于执行 ToList()。

【讨论】:

results 是 ObjectSet 类型。 results.OrderByDescending(sort) 返回 IOrderedEnumerable 因此它不能分配给结果。你给了我一些关于尝试的想法。测试完毕后会发回。 这就是他们所有的回报。直到您指定 .Select() 或 .ToList()。 对,但是编译器会阻止“if (desc) results = results.OrderByDescending(sort)”,因为类型不同。我还发现当我使用 .Skip 而不首先调用 .OrderBy 时出现错误(发生在 !desc 时)。发布了对我原始帖子的编辑以及其他发现。

以上是关于实体框架/Linq to SQL:跳过和采取的主要内容,如果未能解决你的问题,请参考以下文章

在 LINQ to Entities 中有 10 个不同的对象之前,如何跳过和获取对象?

Linq to SQL VS 实体框架

DLinq 演变成哪一个——Linq to SQL 还是实体框架?

将 Linq To Sql DATA 转换为实体框架

带有存储过程的实体框架 VS LINQ to SQL VS ADO.NET? [关闭]

如果不支持包含,您如何在 LINQ to Entities(实体框架)中执行 SQL 样式的“IN”语句?