Подкачка с LINQ для объектов
Как бы вы реализовали подкачку в запросе LINQ? На самом деле на данный момент я был бы удовлетворен, если бы функцию sql TOP можно было имитировать. Тем не менее, я уверен, что необходимость в полной поддержке подкачки возникает рано позже в любом случае.
var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
12 ответов:
Вы ищите
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);
используя
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);
вот мой эффективный подход к подкачке при использовании LINQ to objects:
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
что будет очень неэффективно, если вы заинтересованы в нескольких страницах.
EDIT-Removed Skip (0) as it's not necessary
var queryResult = (from o in objects where ... select new { A = o.a, B = o.b } ).Take(10);
Не знаю, поможет ли это кому-нибудь, но я нашел это полезным для своих целей:
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 авторов за раз.
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
Batchsize, очевидно, будет целым числом. Это использует тот факт, что целые числа просто отбрасывают десятичные разряды.
Я наполовину шучу с этим ответом, но он будет делать то, что вы хотите, и потому что он отложен, вы не понесете большого штрафа за производительность, если вы это сделаете
pages.First(p => p.Key == thePage)
это решение не для 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); } } }
Это полезно, если пропустить или принять не поддерживаются.
Я использую этот метод расширения:
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. хоп это может сделать вещь для вас
есть два основных варианта:
.NET >= 4.0 динамический LINQ:
- добавить с помощью System.В LINQ.Динамический; в верхней части.
- использование:
var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
вы также можете сделать это с помощью NuGet.
.NET Методы Расширения:
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); }