Cercapersone con LINQ per oggetti

Come implementeresti il ​​paging in una query LINQ? In realtà per il momento, sarei soddisfatto se la funzione sql TOP potesse essere imitata. Tuttavia, sono sicuro che la necessità di un supporto completo per il paging si verificherà prima comunque.

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

Stai cercando i metodi di estensione Skip and Take . Skip passa oltre i primi N elementi nel risultato, restituendo il resto; Take restituisce i primi N elementi nel risultato, lasciando cadere tutti gli elementi rimanenti.

Vedere MSDN per ulteriori informazioni su come utilizzare questi metodi: http://msdn.microsoft.com/en-us/library/bb386988.aspx

Per esempio:

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

Usare Skip and Take è sicuramente la strada da percorrere. Se dovessi implementare questo, probabilmente scriverei il mio metodo di estensione per gestire il paging (per rendere il codice più leggibile). L’implementazione può ovviamente utilizzare Skip and Take :

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

La class definisce due metodi di estensione: uno per IEnumerable e uno per IQueryable , il che significa che è ansible utilizzarlo sia con LINQ to Objects e LINQ to SQL (quando si scrive una query di database, il compilatore sceglierà la versione IQueryable ).

A seconda dei requisiti di impaginazione, è inoltre ansible aggiungere un comportamento aggiuntivo (ad esempio per gestire il valore di page o di page negativo). Ecco un esempio di come useresti questo metodo di estensione nella tua query:

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

Ecco il mio approccio performante al paging quando si usa LINQ per oggetti:

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

Questo può quindi essere usato in questo modo:

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

Nessuno di questi rifiuti Skip and Take che sarà altamente inefficiente se sei interessato a più pagine.

  ( for o in objects where ... select new { A=oa, B=ob }) .Skip((page-1)*pageSize) .Take(pageSize) 

EDIT – Rimosso Skip (0) in quanto non è necessario

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

Non so se questo aiuterà qualcuno, ma l’ho trovato utile per i miei scopi:

 private static IEnumerable PagedIterator(IEnumerable 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++; } } 

Per usarlo dovresti avere una query su linq e passare il risultato insieme alla dimensione della pagina in un ciclo 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 } 

Quindi questo andrà a scorrere su ogni autore che recupera 100 autori alla volta.

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

Il lotto sarà ovviamente un numero intero. Ciò si avvantaggia del fatto che i numeri interi lasciano semplicemente i decimali.

Sto scherzando per metà con questa risposta, ma farà quello che vuoi e, poiché è differita, non incorrerai in una grossa penalizzazione delle prestazioni se lo fai

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

Questa soluzione non è per LinqToEntities, non so nemmeno se potrebbe trasformarla in una buona query.

Simile alla risposta di Lukazoid ho creato un’estensione per IQueryable.

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

È utile se Skip or Take non sono supportati.

Io uso questo metodo di estensione:

 public static IQueryable Page(this IQueryable obj, int page, int pageSize, System.Linq.Expressions.Expression> 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 GetAll(int RowIndex, int PageSize, string SortExpression) { int totalRows; int pageIndex = RowIndex / PageSize; List data= new List(); IEnumerable 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(); } 
  public LightDataTable PagerSelection(int pageNumber, int setsPerPage, Func prection = null) { this.setsPerPage = setsPerPage; this.pageNumber = pageNumber > 0 ? pageNumber - 1 : pageNumber; if (!ValidatePagerByPageNumber(pageNumber)) return this; var rowList = rows.Cast(); 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 }; } 

questo è quello che ho fatto. Normalmente inizi su 1 ma in IList inizi con 0. quindi se hai 152 righe significa che hai 8 paging, ma in IList hai solo 7. hop questo può rendere la cosa chiara per te

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

Ci sono due opzioni principali:

.NET> = 4.0 LINQ dinamico :

  1. Aggiungi usando System.Linq.Dynamic; in cima.
  2. Usa: var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

Puoi anche ottenerlo con NuGet .

.NET <4.0 Metodi di estensione :

 private static readonly Hashtable accessors = new Hashtable(); private static readonly Hashtable callSites = new Hashtable(); private static CallSite> GetCallSiteLocked(string name) { var callSite = (CallSite>)callSites[name]; if(callSite == null) { callSites[name] = callSite = CallSite>.Create( Binder.GetMember(CSharpBinderFlags.None, name, typeof(AccessorCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })); } return callSite; } internal static Func GetAccessor(string name) { Func accessor = (Func)accessors[name]; if (accessor == null) { lock (accessors ) { accessor = (Func)accessors[name]; if (accessor == null) { if(name.IndexOf('.') >= 0) { string[] props = name.Split('.'); CallSite>[] 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 OrderBy(this IEnumerable source, string property) { return Enumerable.OrderBy(source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable OrderByDescending(this IEnumerable source, string property) { return Enumerable.OrderByDescending(source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, string property) { return Enumerable.ThenBy(source, AccessorCache.GetAccessor(property), Comparer.Default); } public static IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, string property) { return Enumerable.ThenByDescending(source, AccessorCache.GetAccessor(property), Comparer.Default); }