2010-03-04 3 views
67

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

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

ответ

175

Вы ищете методы расширения 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); 
+5

Должен ли я использовать ту же технику SQL с огромной базой данных, это займет всю таблицу в памяти, а затем выбрасывайте ненужный? – user256890

+1

Если вы заинтересованы в том, что происходит под капотом, то большинство драйверов базы данных LINQ обеспечивают способ получения отладочной информации вывода для реального SQL, который выполняется. –

+0

Rob Conery блог о классе PagedList , который может помочь вам приступить к работе. http://blog.wekeroad.com/blog/aspnet-mvc-pagedlistt/ – jrotello

5

EDIT - Удалены Skip (0), так как нет необходимости

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

Нельзя ли изменить порядок методов Take/Skip? Пропустить (0) после Take не имеет смысла. Thanx для указания вашего примера в стиле запроса. – user256890

+1

Нет, он прав. 'Take' 10,' Skip' 0 принимает первые 10 элементов. 'Пропустить' 0 бессмысленно и никогда не должно быть сделано.И порядок 'Take' и' Skip' имеет значение - 'Skip' 10,' Take' 10 принимает элементы 10-20; 'Take' 10,' Skip' 10 не возвращает никаких элементов. –

+0

@ Давид, мой плохой ты прав. –

8
(for o in objects 
    where ... 
    select new 
    { 
    A=o.a, 
    B=o.b 
    }) 
.Skip((page-1)*pageSize) 
.Take(pageSize) 
44

Использование 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 к объектам и LINQ к SQL (если записывая запрос базы данных, компилятор будет выбирать версию IQueryable).

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

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

Я верю, что это вернет весь набор результатов, а затем отфильтрует в памяти вместо сервера. Огромная производительность по сравнению с базой данных, если это SQL. – jvenema

+1

@jvenema Вы правы. Поскольку это использует интерфейс '' IEnumerable'', а не 'IQueryable', это приведет к потере всей таблицы базы данных, что будет значительным хитом производительности. –

+1

Вы можете, конечно, легко добавить перегруз для 'IQueryable', чтобы он работал с запросами databse (я отредактировал ответ и добавил его). Немного досадно, что вы не можете полностью писать код (в Haskell это возможно с классами типов). Исходный вопрос, упомянутый LINQ to Objects, поэтому я написал только одну перегрузку. –

3

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

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 запрос и передать результат вместе с размером страницы в петлю Еогеаспа:

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

+0

Поскольку Count() перечисляет коллекцию, вы можете просто преобразовать ее в List() и итерации с помощью индексов. – Kaerber

21

Вот мой производительный подход к пейджинговой при использовании 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  
    } 
} 

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

+2

Это должен быть принятый ответ! – tdc

+0

Он работает в Entity Framework с Azure SQL Data Warehouse, который не поддерживает метод Skip (внутренне используя предложение OFFSET) –

+1

Это просто нужно было украсть и поместить в мою общую библиотеку, спасибо! Я просто переименовал метод в 'Paginate' для удаления' noun' vs ' глагол 'неопределенность. – Gabrielius

0

Я использую этот метод расширения:

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(); 
} 
+1

Хороший ответ требует большего объяснения. – Dinistro

1
var pages = items.Select((item, index) => new { item, Page = index/batchSize }).GroupBy(g => g.Page); 

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

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

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

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

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

это то, что я сделал. Normaly вы начинаете в 1, но в IList начать с 0. так, если у вас есть 152 строк, что означает, что вы имеете 8 пейджинг, но в IList у вас есть только 7. хоп это может сделать вещь ясно для вас

0

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

0

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

.NET> = 4,0 Dynamic LINQ:

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

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

.NET < 4,0 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); 
} 
Смежные вопросы