Пейджинг с LINQ для объектов - PullRequest
80 голосов
/ 04 марта 2010

Как бы вы реализовали пейджинг в запросе LINQ? На самом деле, я был бы удовлетворен, если бы имитировалась функция sql TOP. Однако я уверен, что в любом случае необходимость в полной поддержке подкачки возникает раньше.

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

Ответы [ 12 ]

201 голосов
/ 04 марта 2010

Вы ищете методы расширения Skip и Take. Skip перемещается за первые N элементов в результате, возвращая остаток; Take возвращает первые N элементов в результате, удаляя оставшиеся элементы.

См. MSDN для получения дополнительной информации о том, как использовать эти методы: http://msdn.microsoft.com/en-us/library/bb386988.aspx

Например:

int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
  .Skip(numberOfObjectsPerPage * pageNumber)
  .Take(numberOfObjectsPerPage);
50 голосов
/ 04 марта 2010

Использование Skip и Take - определенно путь. Если бы я реализовывал это, я, вероятно, написал бы свой собственный метод расширения для обработки подкачки страниц (чтобы сделать код более читабельным). Реализация может, конечно, использовать Skip и Take:

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 (при записи запроса к базе данных компилятор выберет IQueryable версия).

В зависимости от ваших требований к подкачке, вы также можете добавить некоторые дополнительные функции (например, для обработки отрицательного значения pageSize или page). Вот пример того, как вы будете использовать этот метод расширения в вашем запросе:

var q = (from p in products
         where p.Show == true
         select new { p.Name }).Page(10, pageIndex);
30 голосов
/ 06 марта 2014

Вот мой эффективный подход к подкачке страниц при использовании LINQ для объектов:

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

Ничего из этогоTake, что будет крайне неэффективно, если вы заинтересованы в нескольких страницах.

8 голосов
/ 04 марта 2010
   ( for o in objects
    where ...
    select new
   {
     A=o.a,
     B=o.b
   })
.Skip((page-1)*pageSize)
.Take(pageSize)
5 голосов
/ 07 декабря 2012

Не знаю, поможет ли это кому-нибудь, но я нашел это полезным для моих целей:

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 авторов одновременно.

5 голосов
/ 04 марта 2010

РЕДАКТИРОВАТЬ - Удален Пропустить (0), поскольку это не нужно

var queryResult = (from o in objects where ...
                      select new
                      {
                          A = o.a,
                          B = o.b
                      }
                  ).Take(10);
3 голосов
/ 04 июля 2015
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);

Размер пакета, очевидно, будет целым числом. Это использует тот факт, что целые числа просто отбрасывают десятичные разряды.

Я наполовину шучу с этим ответом, но он будет делать то, что вы хотите, и, поскольку он отложен, вы не понесете большого ущерба производительности, если вы сделаете

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

Это решение не для LinqToEntities, я даже не знаю, может ли оно превратить это в хороший запрос.

2 голосов
/ 18 марта 2018

Аналогично Ответ Lukazoid Я создал расширение для 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 не поддерживаются.

1 голос
/ 27 июня 2017

Есть два основных варианта:

.NET> = 4,0 Динамический LINQ :

  1. Добавить с помощью System.Linq.Dynamic; наверху.
  2. Использование: var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

Вы также можете получить его по NuGet .

.NET <4.0 </strong> Методы расширения :

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);
}
1 голос
/ 04 февраля 2016

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