Wie würden Sie Paging in einer LINQ-Abfrage implementieren? Eigentlich wäre ich vorerst zufrieden, wenn die SQL TOP-Funktion imitiert werden könnte. Ich bin mir jedoch sicher, dass der Bedarf an vollständiger Paging-Unterstützung ohnehin früher besteht.
var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
Sie suchen nach den Erweiterungsmethoden Skip
und Take
. Skip
bewegt sich an den ersten N Elementen im Ergebnis vorbei und gibt den Rest zurück. Take
gibt die ersten N Elemente im Ergebnis zurück und löscht alle verbleibenden Elemente.
Weitere Informationen zur Verwendung dieser Methoden finden Sie unter MSDN: http://msdn.Microsoft.com/en-us/library/bb386988.aspx
Zum Beispiel:
int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
.Skip(numberOfObjectsPerPage * pageNumber)
.Take(numberOfObjectsPerPage);
Die Verwendung von Skip
und Take
ist definitiv der richtige Weg. Wenn ich dies implementieren würde, würde ich wahrscheinlich eine eigene Erweiterungsmethode schreiben, um mit dem Paging (um den Code lesbarer zu machen) umzugehen. Die Implementierung kann natürlich Skip
und Take
verwenden:
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);
}
}
Die Klasse definiert zwei Erweiterungsmethoden - eine für IEnumerable
und eine für IQueryable
. Dies bedeutet, dass Sie sie sowohl mit LINQ to Objects als auch mit LINQ to SQL verwenden können (beim Schreiben einer Datenbankabfrage wählt der Compiler die IQueryable
-Version aus).
Abhängig von Ihren Paging-Anforderungen können Sie auch zusätzliches Verhalten hinzufügen (z. B. um negative pageSize
- oder page
-Werte zu behandeln). Hier ein Beispiel, wie Sie diese Erweiterungsmethode in Ihrer Abfrage verwenden würden:
var q = (from p in products
where p.Show == true
select new { p.Name }).Page(10, pageIndex);
Hier ist mein performanter Ansatz zum Paging, wenn LINQ für Objekte verwendet wird:
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);
}
}
}
Dies kann dann so verwendet werden:
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
}
}
Keiner dieser Müll Skip
und Take
, der äußerst ineffizient sein wird, wenn Sie sich für mehrere Seiten interessieren.
( for o in objects
where ...
select new
{
A=o.a,
B=o.b
})
.Skip((page-1)*pageSize)
.Take(pageSize)
Ich weiß nicht, ob das jemandem helfen wird, aber ich fand es nützlich für meine Zwecke:
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++;
}
}
Um dies zu verwenden, hätten Sie eine linq-Abfrage und übergeben das Ergebnis zusammen mit der Seitengröße in einer foreach-Schleife:
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
}
Dies wird also jeden Autor durchlaufen, der 100 Autoren gleichzeitig abruft.
BEARBEITEN - Übersprungen (0) wurde entfernt, da dies nicht erforderlich ist
var queryResult = (from o in objects where ...
select new
{
A = o.a,
B = o.b
}
).Take(10);
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
Die Batchgröße wird offensichtlich eine ganze Zahl sein. Dadurch wird die Tatsache ausgenutzt, dass Ganzzahlen einfach Dezimalstellen weglassen.
Ich mache nur einen Scherz mit dieser Antwort, aber sie wird tun, was Sie wollen, und weil sie verzögert wird, werden Sie keinen großen Leistungsnachteil erleiden, wenn Sie dies tun
pages.First(p => p.Key == thePage)
Diese Lösung ist nicht für LinqToEntities, ich weiß nicht einmal, ob daraus eine gute Abfrage werden könnte.
Ähnlich wie Lukazoids Antwort Ich habe eine Erweiterung für IQueryable erstellt.
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);
}
}
}
Dies ist nützlich, wenn Skip oder Take nicht unterstützt werden.
Es gibt zwei Hauptoptionen:
.NET> = 4.0Dynamic LINQ :
var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Sie können es auch über NuGet erhalten.
.NET <4.0Erweiterungsmethoden :
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);
}
Ich benutze diese Erweiterungsmethode:
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 };
}
normalerweise fangen Sie bei 1 an, aber in IList beginnen Sie mit 0 . Wenn Sie also 152 Zeilen haben, bedeutet das, dass Sie 8 Paging haben. In IList haben Sie jedoch nur 7 ..__ kann die Sache für Sie klar machen
var results = (medicineInfo.OrderBy(x=>x.id)
.Skip((pages -1) * 2)
.Take(2));