Jak zaimplementowałbyś stronicowanie w zapytaniu LINQ? Właściwie na razie byłbym zadowolony, gdyby można było naśladować funkcję sql TOP. Jestem jednak pewien, że potrzeba pełnej obsługi stronicowania i tak pojawi się wcześniej.
var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
Korzystanie
Skip
iTake
jest zdecydowanie właściwą drogą. Gdybym to implementował, prawdopodobnie napisałbym własną metodę rozszerzenia do obsługi stronicowania (aby kod był bardziej czytelny). Implementacja może oczywiście korzystaćSkip
iTake
: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); } }
Klasa definiuje dwie metody rozszerzające - jedną for
IEnumerable
i jedną forIQueryable
, co oznacza, że można jej używać zarówno z LINQ to Objects, jak i LINQ to SQL (podczas pisania zapytania do bazy danych kompilator wybierzeIQueryable
wersję).W zależności od wymagań dotyczących stronicowania możesz także dodać dodatkowe zachowanie (na przykład obsługę wartości ujemnych
pageSize
lubpage
wartości). Oto przykład, jak można użyć tej metody rozszerzenia w zapytaniu:var q = (from p in products where p.Show == true select new { p.Name }).Page(10, pageIndex);
źródło
IEnumerable
interfejsu, a nieIQueryable
spowoduje to ściągnięcia całej tabeli bazy danych, co będzie dużym spadkiem wydajności.IQueryable
aby działało również z zapytaniami z bazy danych (poprawiłem odpowiedź i dodałem ją). Trochę niefortunne jest to, że nie możesz napisać kodu w sposób w pełni ogólny (w Haskell byłoby to możliwe w przypadku klas typów). Oryginalne pytanie dotyczyło LINQ to Objects, więc napisałem tylko jedno przeciążenie.Oto moje wydajne podejście do stronicowania podczas korzystania z LINQ do obiektów:
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); } } }
Można to następnie wykorzystać w następujący sposób:
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 } }
Żadna z tych śmieci
Skip
iTake
które będą wysoce nieefektywne, jeśli jesteś zainteresowany w wielu stronach.źródło
Paginate
aby usunąćnoun
vsverb
niejednoznaczność.( for o in objects where ... select new { A=o.a, B=o.b }) .Skip((page-1)*pageSize) .Take(pageSize)
źródło
Nie wiem, czy to komuś pomoże, ale uznałem to za przydatne do moich celów:
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++; } }
Aby tego użyć, należałoby wykonać zapytanie linq i przekazać wynik wraz z rozmiarem strony do pętli 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 }
Więc to będzie iterować przez każdego autora, pobierając 100 autorów na raz.
źródło
EDYCJA - Usunięto Pomiń (0), ponieważ nie jest to konieczne
var queryResult = (from o in objects where ... select new { A = o.a, B = o.b } ).Take(10);
źródło
Take
10,Skip
0 przyjmuje pierwsze 10 elementów.Skip
0 jest bezcelowe i nigdy nie powinno się go robić. A kolejnośćTake
i maSkip
znaczenie -Skip
10,Take
10 przyjmuje elementy 10-20;Take
10,Skip
10 nie zwraca żadnych elementów.var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
Wielkość partii będzie oczywiście liczbą całkowitą. Wykorzystuje to fakt, że liczby całkowite po prostu pomijają miejsca dziesiętne.
W połowie żartuję z tą odpowiedzią, ale zrobi to, co chcesz, a ponieważ jest odroczona, nie poniesiesz dużej kary za wydajność, jeśli to zrobisz
To rozwiązanie nie jest przeznaczone dla LinqToEntities, nie wiem nawet, czy może to przekształcić w dobre zapytanie.
źródło
Podobnie jak odpowiedź Lukazoida, stworzyłem rozszerzenie dla 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); } } }
Jest to przydatne, jeśli opcja Skip lub Take nie jest obsługiwana.
źródło
Używam tej metody rozszerzenia:
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(); }
źródło
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 }; }
to właśnie zrobiłem. Normalnie zaczynasz od 1, ale w IList zaczynasz od 0, więc jeśli masz 152 wiersze, to znaczy, że masz 8 stronicowania, ale w IList masz tylko 7. mam nadzieję, że to wszystko wyjaśni
źródło
źródło
Istnieją dwie główne opcje:
.NET> = 4.0 Dynamic LINQ :
var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Możesz go również uzyskać za pomocą NuGet .
Metody rozszerzeń .NET <4.0 :
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); }
źródło