Как бы вы реализовали разбиение на страницы в запросе LINQ? Собственно, пока я был бы удовлетворен, если бы можно было имитировать функцию sql TOP. Однако я уверен, что потребность в полной поддержке пейджинга в любом случае возникнет раньше.
var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
Использование
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);
источник
IEnumerable
интерфейс, а не приIQueryable
этом, будет задействована вся таблица базы данных, что существенно снизит производительность.IQueryable
чтобы заставить его работать и с запросами к базе данных (я отредактировал ответ и добавил его). Немного жаль, что вы не можете написать код полностью универсальным способом (в Haskell это было бы возможно с классами типов). В исходном вопросе упоминался LINQ to Objects, поэтому я написал только одну перегрузку.Вот мой эффективный подход к разбиению на страницы при использовании 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
это будет крайне неэффективно, если вас интересуют несколько страниц.источник
Paginate
для удаленияnoun
противverb
двусмысленности.( for o in objects where ... select new { A=o.a, B=o.b }) .Skip((page-1)*pageSize) .Take(pageSize)
источник
Не знаю, поможет ли это кому-нибудь, но я нашел это полезным для своих целей:
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 авторов за раз.
источник
РЕДАКТИРОВАТЬ - Удален пропуск (0), поскольку он не нужен
var queryResult = (from o in objects where ... select new { A = o.a, B = o.b } ).Take(10);
источник
Take
10,Skip
0 занимает первые 10 элементов.Skip
0 бессмысленно и никогда не должен выполняться. А порядокTake
иSkip
имеет значение -Skip
10,Take
10 занимает элементы 10-20;Take
10,Skip
10 не возвращает никаких элементов.var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
Размер партии, очевидно, будет целым числом. Это использует тот факт, что целые числа просто отбрасывают десятичные знаки.
Я наполовину шучу над этим ответом, но он будет делать то, что вы хотите, и, поскольку он отложен, вы не понесете большого ущерба производительности, если сделаете
Это решение не для LinqToEntities, я даже не знаю, сможет ли оно превратить это в хороший запрос.
источник
Как и в ответе 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 не поддерживаются.
источник
Я использую такой способ расширения:
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(); }
источник
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. hop, это может прояснить вам ситуацию.
источник
источник
Есть два основных варианта:
.NET> = 4.0 Динамический LINQ :
var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Вы также можете получить его через NuGet .
Методы расширения .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); }
источник