【问题标题】:Paging with LINQ for objects使用 LINQ 对对象进行分页
【发布时间】:2011-01-23 17:41:54
【问题描述】:

如何在 LINQ 查询中实现分页? 其实就目前而言,如果能模仿sql TOP函数我就很满意了。不过,我相信无论如何,对全面分页支持的需求迟早会出现。

var queryResult = from o in objects
                  where ...
                  select new
                      {
                         A = o.a,
                         B = o.b
                      }
                   ????????? TOP 10????????

【问题讨论】:

    标签: c# .net linq paging


    【解决方案1】:

    您正在寻找 SkipTake 扩展方法。 Skip 移过结果中的前 N ​​个元素,返回余数; Take 返回结果中的前 N ​​个元素,删除所有剩余元素。

    有关如何使用这些方法的更多信息,请参阅 MSDN:http://msdn.microsoft.com/en-us/library/bb386988.aspx

    假设您已经考虑到 pageNumber 应该从 0 开始(按照 cmets 中的建议每减少 1),您可以这样做:

    int numberOfObjectsPerPage = 10;
    var queryResultPage = queryResult
      .Skip(numberOfObjectsPerPage * pageNumber)
      .Take(numberOfObjectsPerPage);
    

    否则,如果 pageNumber 是从 1 开始的(如 @Alvin 所建议的那样)

    int numberOfObjectsPerPage = 10;
    var queryResultPage = queryResult
      .Skip(numberOfObjectsPerPage * (pageNumber - 1))
      .Take(numberOfObjectsPerPage);
    

    【讨论】:

    • 我是否应该在 SQL 上使用与大型数据库相同的技术,它会先将整个表放入内存然后丢弃不需要的吗?
    • 如果您对幕后发生的事情感兴趣,顺便说一下,大多数 LINQ 数据库驱动程序都提供了一种方法来获取正在执行的实际 SQL 的调试输出信息。
    • Rob Conery 写了一篇关于 PagedList 类的博客,它可以帮助您入门。 blog.wekeroad.com/blog/aspnet-mvc-pagedlistt
    • 如果 pageNumber 不为零 (0),这将导致跳过第一页。如果 pageNumber 以 1 开头,则使用此“.Skip(numberOfObjectsPerPage * (pageNumber - 1))”
    • 查询到数据库的 SQL 会是什么样子?
    【解决方案2】:

    使用SkipTake 绝对是要走的路。如果我正在实现它,我可能会编写自己的扩展方法来处理分页(以使代码更具可读性)。实现当然可以使用SkipTake

    static class PagingUtils {
      public static IEnumerable<T> Page<T>(this IEnumerable<T> en, int pageSize, int page) {
        return en.Skip(page * pageSize).Take(pageSize);
      }
      public static IQueryable<T> Page<T>(this IQueryable<T> en, int pageSize, int page) {
        return en.Skip(page * pageSize).Take(pageSize);
      }
    }
    

    该类定义了两种扩展方法-一种用于IEnumerable,一种用于IQueryable,这意味着您可以将它与LINQ to Objects和LINQ to SQL一起使用(编写数据库查询时,编译器将选择@ 987654328@版本)。

    根据您的分页要求,您还可以添加一些额外的行为(例如处理负的pageSizepage 值)。下面是一个示例,您将如何在查询中使用此扩展方法:

    var q = (from p in products
             where p.Show == true
             select new { p.Name }).Page(10, pageIndex);
    

    【讨论】:

    • 我相信这将返回整个结果集,然后在内存中而不是在服务器上过滤。如果这是 SQL,则会对数据库造成巨大的性能损失。
    • @jvenema 你是对的。由于这是使用IEnumerable 接口而不是IQueryable,这将拉入整个数据库表,这将对性能造成重大影响。
    • 您当然可以轻松地为IQueryable 添加一个重载,使其也适用于数据库查询(我编辑了答案并添加了它)。不幸的是,您不能以完全通用的方式编写代码(在 Haskell 中,类型类可以做到这一点)。原来的问题提到了LINQ to Objects,所以我只写了一个重载。
    • 我只是想自己实现这个。我有点惊讶它不是标准实现的一部分。感谢您的示例代码!
    • 我觉得例子应该是:public static IQueryable Page(...etc
    【解决方案3】:

    这是我在使用 LINQ to 对象时进行分页的高效方法:

    public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int pageSize)
    {
        Contract.Requires(source != null);
        Contract.Requires(pageSize > 0);
        Contract.Ensures(Contract.Result<IEnumerable<IEnumerable<T>>>() != null);
    
        using (var enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                var currentPage = new List<T>(pageSize)
                {
                    enumerator.Current
                };
    
                while (currentPage.Count < pageSize && enumerator.MoveNext())
                {
                    currentPage.Add(enumerator.Current);
                }
                yield return new ReadOnlyCollection<T>(currentPage);
            }
        }
    }
    

    然后可以这样使用:

    var items = Enumerable.Range(0, 12);
    
    foreach(var page in items.Page(3))
    {
        // Do something with each page
        foreach(var item in page)
        {
            // Do something with the item in the current page       
        }
    }
    

    如果您对多个页面感兴趣,这些垃圾SkipTake 将非常低效。

    【讨论】:

    • 它在带有 Azure SQL 数据仓库的实体框架中工作,不支持 Skip 方法(内部使用 OFFSET 子句)
    • 这只是被盗并放入我的公共库中,谢谢!我只是将方法重命名为 Paginate 以消除 nounverb 的歧义。
    • 我想知道这句话是否成立。现在已经好几年了,SkipTake优化了吗?
    • 现在在 .NET 6 中,我们拥有与此处的 Page 相同的 Chunk 方法:items.Chunk(3)
    【解决方案4】:
       ( for o in objects
        where ...
        select new
       {
         A=o.a,
         B=o.b
       })
    .Skip((page-1)*pageSize)
    .Take(pageSize)
    

    【讨论】:

      【解决方案5】:

      不知道这是否会帮助任何人,但我发现它对我的目的很有用:

      private static IEnumerable<T> PagedIterator<T>(IEnumerable<T> objectList, int PageSize)
      {
          var page = 0;
          var recordCount = objectList.Count();
          var pageCount = (int)((recordCount + PageSize)/PageSize);
      
          if (recordCount < 1)
          {
              yield break;
          }
      
          while (page < pageCount)
          {
              var pageData = objectList.Skip(PageSize*page).Take(PageSize).ToList();
      
              foreach (var rd in pageData)
              {
                  yield return rd;
              }
              page++;
          }
      }
      

      要使用它,您需要一些 linq 查询,并将结果与​​页面大小一起传递到 foreach 循环中:

      var results = from a in dbContext.Authors
                    where a.PublishDate > someDate
                    orderby a.Publisher
                    select a;
      
      foreach(var author in PagedIterator(results, 100))
      {
          // Do Stuff
      }
      

      所以这将遍历每个作者,一次获取 100 个作者。

      【讨论】:

      • 当 Count() 枚举集合时,您也可以将其转换为 List() 并使用索引进行迭代。
      【解决方案6】:

      编辑 - 删除 Skip(0),因为它不是必需的

      var queryResult = (from o in objects where ...
                            select new
                            {
                                A = o.a,
                                B = o.b
                            }
                        ).Take(10);
      

      【讨论】:

      • 您不应该更改 Take/Skip 方法的顺序吗? Take 之后的 Skip(0) 没有意义。感谢您以查询方式提供示例。
      • 不,他是对的。 Take 10, Skip 0 采用前 10 个元素。 Skip 0 毫无意义,永远不应该这样做。 TakeSkip 的顺序很重要 -- Skip 10, Take 10 需要元素 10-20; Take 10, Skip 10 不返回任何元素。
      • 在调用 Take 之前,您可能还需要将查询括起来。 (从...选择...)。Take(10)。我通过选择一个字符串来调用构造。没有括号,Take 返回字符串的前 10 个字符,而不是限制查询结果:)
      【解决方案7】:
      var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
      

      Batchsize 显然是一个整数。这利用了整数简单地去掉小数位这一事实。

      我对这个响应半开玩笑,但它会做你想做的事,而且因为它是延迟的,如果你这样做,你不会受到很大的性能损失

      pages.First(p => p.Key == thePage)
      

      此解决方案不适用于 LinqToEntities,我什至不知道它是否可以将其变成一个好的查询。

      【讨论】:

        【解决方案8】:

        类似于Lukazoid's answer 我已经为 IQueryable 创建了一个扩展。

           public static IEnumerable<IEnumerable<T>> PageIterator<T>(this IQueryable<T> source, int pageSize)
                    {
                        Contract.Requires(source != null);
                        Contract.Requires(pageSize > 0);
                        Contract.Ensures(Contract.Result<IEnumerable<IQueryable<T>>>() != null);
        
                        using (var enumerator = source.GetEnumerator())
                        {
                            while (enumerator.MoveNext())
                            {
                                var currentPage = new List<T>(pageSize)
                                {
                                    enumerator.Current
                                };
        
                                while (currentPage.Count < pageSize && enumerator.MoveNext())
                                {
                                    currentPage.Add(enumerator.Current);
                                }
                                yield return new ReadOnlyCollection<T>(currentPage);
                            }
                        }
                    }
        

        如果不支持 Skip 或 Take,这很有用。

        【讨论】:

          【解决方案9】:

          我使用这个扩展方法:

          public static IQueryable<T> Page<T, TResult>(this IQueryable<T> obj, int page, int pageSize, System.Linq.Expressions.Expression<Func<T, TResult>> keySelector, bool asc, out int rowsCount)
          {
              rowsCount = obj.Count();
              int innerRows = rowsCount - (page * pageSize);
              if (innerRows < 0)
              {
                  innerRows = 0;
              }
              if (asc)
                  return obj.OrderByDescending(keySelector).Take(innerRows).OrderBy(keySelector).Take(pageSize).AsQueryable();
              else
                  return obj.OrderBy(keySelector).Take(innerRows).OrderByDescending(keySelector).Take(pageSize).AsQueryable();
          }
          
          public IEnumerable<Data> GetAll(int RowIndex, int PageSize, string SortExpression)
          {
              int totalRows;
              int pageIndex = RowIndex / PageSize;
          
              List<Data> data= new List<Data>();
              IEnumerable<Data> dataPage;
          
              bool asc = !SortExpression.Contains("DESC");
              switch (SortExpression.Split(' ')[0])
              {
                  case "ColumnName":
                      dataPage = DataContext.Data.Page(pageIndex, PageSize, p => p.ColumnName, asc, out totalRows);
                      break;
                  default:
                      dataPage = DataContext.vwClientDetails1s.Page(pageIndex, PageSize, p => p.IdColumn, asc, out totalRows);
                      break;
              }
          
              foreach (var d in dataPage)
              {
                  clients.Add(d);
              }
          
              return data;
          }
          public int CountAll()
          {
              return DataContext.Data.Count();
          }
          

          【讨论】:

            【解决方案10】:
                public LightDataTable PagerSelection(int pageNumber, int setsPerPage, Func<LightDataRow, bool> prection = null)
                {
                    this.setsPerPage = setsPerPage;
                    this.pageNumber = pageNumber > 0 ? pageNumber - 1 : pageNumber;
                    if (!ValidatePagerByPageNumber(pageNumber))
                        return this;
            
                    var rowList = rows.Cast<LightDataRow>();
                    if (prection != null)
                        rowList = rows.Where(prection).ToList();
            
                    if (!rowList.Any())
                        return new LightDataTable() { TablePrimaryKey = this.tablePrimaryKey };
                    //if (rowList.Count() < (pageNumber * setsPerPage))
                    //    return new LightDataTable(new LightDataRowCollection(rowList)) { TablePrimaryKey = this.tablePrimaryKey };
            
                    return new LightDataTable(new LightDataRowCollection(rowList.Skip(this.pageNumber * setsPerPage).Take(setsPerPage).ToList())) { TablePrimaryKey = this.tablePrimaryKey };
              }
            

            这就是我所做的。 通常你从 1 开始,但在 IList 中你从 0 开始。 因此,如果您有 152 行,这意味着您有 8 个分页,但在 IList 中您只有 7 个。 跳这可以让你清楚

            【讨论】:

              【解决方案11】:

              var results = (medicineInfo.OrderBy(x=>x.id)
                                     .Skip((pages -1) * 2)
                                     .Take(2));

              【讨论】:

                【解决方案12】:

                有两个主要选项:

                .NET >= 4.0 Dynamic LINQ:

                1. 使用 System.Linq.Dynamic 添加;在顶部。
                2. 使用:var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

                您也可以通过NuGet获取。

                .NET Extension Methods:

                private static readonly Hashtable accessors = new Hashtable();
                
                private static readonly Hashtable callSites = new Hashtable();
                
                private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(string name) {
                    var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
                    if(callSite == null)
                    {
                        callSites[name] = callSite = CallSite<Func<CallSite, object, object>>.Create(
                                    Binder.GetMember(CSharpBinderFlags.None, name, typeof(AccessorCache),
                                new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
                    }
                    return callSite;
                }
                
                internal static Func<dynamic,object> GetAccessor(string name)
                {
                    Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
                    if (accessor == null)
                    {
                        lock (accessors )
                        {
                            accessor = (Func<dynamic, object>)accessors[name];
                            if (accessor == null)
                            {
                                if(name.IndexOf('.') >= 0) {
                                    string[] props = name.Split('.');
                                    CallSite<Func<CallSite, object, object>>[] arr = Array.ConvertAll(props, GetCallSiteLocked);
                                    accessor = target =>
                                    {
                                        object val = (object)target;
                                        for (int i = 0; i < arr.Length; i++)
                                        {
                                            var cs = arr[i];
                                            val = cs.Target(cs, val);
                                        }
                                        return val;
                                    };
                                } else {
                                    var callSite = GetCallSiteLocked(name);
                                    accessor = target =>
                                    {
                                        return callSite.Target(callSite, (object)target);
                                    };
                                }
                                accessors[name] = accessor;
                            }
                        }
                    }
                    return accessor;
                }
                public static IOrderedEnumerable<dynamic> OrderBy(this IEnumerable<dynamic> source, string property)
                {
                    return Enumerable.OrderBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
                }
                public static IOrderedEnumerable<dynamic> OrderByDescending(this IEnumerable<dynamic> source, string property)
                {
                    return Enumerable.OrderByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
                }
                public static IOrderedEnumerable<dynamic> ThenBy(this IOrderedEnumerable<dynamic> source, string property)
                {
                    return Enumerable.ThenBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
                }
                public static IOrderedEnumerable<dynamic> ThenByDescending(this IOrderedEnumerable<dynamic> source, string property)
                {
                    return Enumerable.ThenByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
                }
                

                【讨论】:

                  猜你喜欢
                  • 2015-12-01
                  • 1970-01-01
                  • 2010-09-05
                  • 1970-01-01
                  • 2013-09-05
                  • 2013-06-09
                  • 1970-01-01
                  • 2017-12-15
                  相关资源
                  最近更新 更多