LINQ ile nesneler için sayfalama


94

Bir LINQ sorgusunda sayfalamayı nasıl uygularsınız? Aslında şimdilik sql TOP işlevi taklit edilebilirse memnun olurum. Bununla birlikte, tam sayfalama desteğine duyulan ihtiyacın daha geç ortaya çıkacağına eminim.

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

Yanıtlar:


234

SkipVe Takeuzatma yöntemlerini arıyorsunuz . Skipsonuçtaki ilk N öğeyi geçerek kalanı döndürür; Takesonuçtaki ilk N öğeyi, kalan öğeleri bırakarak döndürür.

Bu yöntemlerin nasıl kullanılacağı hakkında daha fazla bilgi için MSDN'ye bakın: http://msdn.microsoft.com/en-us/library/bb386988.aspx

Sayfa numarasının 0'dan başlaması gerektiğini zaten hesaba kattığınızı varsayarsak (yorumlarda önerildiği gibi 1'e düşürün) Bunu şu şekilde yapabilirsiniz:

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

@Alvin tarafından önerildiği gibi

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

7
Aynı tekniği büyük bir veritabanı ile SQL üzerinden kullanmalı mıyım, önce tüm tabloyu belleğe alıp sonra istenmeyenleri atacak mı?
user256890

1
Bu arada, başlık altında neler olup bittiğiyle ilgileniyorsanız, çoğu LINQ veritabanı sürücüsü, çalıştırılan gerçek SQL için hata ayıklama çıktı bilgilerini almak için bir yol sağlar.
David Pfeffer

Rob Conery, başlamanıza yardımcı olabilecek bir PagedList <T> sınıfı hakkında blog yazdı. blog.wekeroad.com/blog/aspnet-mvc-pagedlistt
jrotello

49
bu, IF pageNumber sıfır (0) tabanlı değilse ilk sayfanın atlanmasıyla sonuçlanacaktır. pageNumber 1 ile başlıyorsa, bu nedenle bunu kullanın ".Skip (numberOfObjectsPerPage * (pageNumber - 1))"
Alvin

Ortaya çıkan SQL, veritabanına ulaşan SQL nasıl olacak?
Faiz

54

Kullanılması Skipve Takekesinlikle gitmek için bir yoldur. Bunu uyguluyor olsaydım, sayfalamayı işlemek için muhtemelen kendi uzantı yöntemimi yazardım (kodu daha okunaklı hale getirmek için). Uygulama elbette Skipve Takeşunları kullanabilir :

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

Sınıf iki uzatma yöntemini tanımlar - biri for IEnumerableve diğeri for IQueryable, bu, onu hem LINQ to Objects hem de LINQ to SQL ile kullanabileceğiniz anlamına gelir (veritabanı sorgusu yazarken, derleyici IQueryablesürümü seçecektir).

Çağrı gereksinimlerinize bağlı olarak, bazı ek davranışlar da ekleyebilirsiniz (örneğin, negatif pageSizeveya pagedeğeri işlemek için ). İşte sorgunuzda bu uzantı yöntemini nasıl kullanacağınızla ilgili bir örnek:

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

3
Bunun tüm sonuç kümesini döndüreceğine ve ardından sunucu yerine bellek içi filtreye gideceğine inanıyorum. Bu SQL ise, bir veritabanına büyük performans çarptı.
jvenema

1
@jvenema Haklısın. Bunun IEnumerableyerine arayüzü kullandığından, IQueryableveritabanı tablosunun tamamını çekecek ve bu da performans açısından büyük bir darbe olacaktır.
David Pfeffer

2
IQueryableVeri tabanı sorgularıyla da çalışmasını sağlamak için elbette kolayca bir aşırı yük ekleyebilirsiniz (cevabı düzenledim ve ekledim). Kodu tamamen genel bir şekilde yazamamanız biraz talihsizdir (Haskell'de bu, tür sınıflarıyla mümkün olabilir). Orijinal soru LINQ to Objects'ten bahsediyordu, bu yüzden sadece bir aşırı yükleme yazdım.
Tomas Petricek

Bunu kendim uygulamayı düşünüyordum. Standart uygulamanın bir parçası olmamasına biraz şaşırdım. Örnek kod için teşekkürler!
Michael Richardson

1
Sanırım örnek şöyle olmalı: public static IQueryable <T> Page <T> (... etc
David Talbot

37

LINQ to nesnelere kullanırken sayfalamaya yönelik performant yaklaşımım:

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

Bu daha sonra şu şekilde kullanılabilir:

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

Bu saçmalıkların hiçbiri Skipve Takebirden çok sayfayla ilgileniyorsanız bunlar oldukça verimsiz olacaktır.


1
Atlama yöntemini desteklemeyen Azure SQL Veri Ambarı ile Entity Framework'te çalışır (dahili olarak OFFSET cümlesini kullanarak)
Michael Freidgeim

4
Bunun çalınması ve ortak kütüphaneme koyulması gerekiyordu, teşekkürler! Sadece yöntemi değiştirildi Paginatekaldırmak nounvs verbbelirsizlik.
Gabrielius

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

6

Bunun kimseye yardımcı olup olmayacağını bilmiyorum, ancak amaçlarım için yararlı buldum:

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

Bunu kullanmak için bir linq sorgunuz olur ve sonucu sayfa boyutuyla birlikte foreach döngüsüne iletirsiniz:

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
}

Yani bu, bir seferde 100 yazar getiren her yazar üzerinde yinelenecek.


Count () koleksiyonu numaralandırırken, onu List () 'e dönüştürebilir ve dizinlerle yineleyebilirsiniz.
Kaerber

5

DÜZENLE - Gerekli olmadığından Atla (0) kaldırıldı

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

2
Al / Atla yöntemlerinin sırasını değiştirmeniz gerekmez mi? Take'den sonra (0) atlamak mantıklı değil. Örneğinizi sorgu stilinde verdiğiniz için teşekkürler.
user256890

2
Hayır, o haklı. Take10, Skip0 ilk 10 öğeyi alır. Skip0 anlamsızdır ve asla yapılmamalıdır. Ve sıralaması Takeve Skipönemli - Skip10, Take10, 10-20 öğelerini alır; Take10, Skip10 hiçbir öğe döndürmez.
David Pfeffer

Take'i çağırmadan önce sorgunun etrafında köşeli parantezlere de ihtiyacınız olabilir. (kimden ... seçin ...). Yapıyı bir dizge seçerek çağırdım. Köşeli parantez olmadan, Take, sorgu sonucunu sınırlamak yerine dizenin ilk 10 karakterini döndürdü :)
user256890

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

Toplu iş boyutu açıkça bir tamsayı olacaktır. Bu, tam sayıların sadece ondalık basamakları düşürmesi gerçeğinden yararlanır.

Bu yanıtı yarı yarıya şaka yapıyorum, ancak istediğinizi yapacak ve ertelendiği için, yaparsanız büyük bir performans cezasına maruz kalmayacaksınız

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

Bu çözüm LinqToEntities için değil, bunu iyi bir sorguya dönüştürebileceğini bile bilmiyorum.


3

Lukazoid'in cevabına benzer şekilde IQueryable için bir uzantı oluşturdum.

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

Atla veya Al desteklenmiyorsa kullanışlıdır.


1

Bu uzantı yöntemini kullanıyorum:

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();
}

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

yaptığım bu. Normalde 1'den başlarsınız, ancak IList'te 0 ile başlarsınız. Bu nedenle, 8 sayfalamanız olduğu anlamına gelen 152 satırınız varsa, ancak IList'te yalnızca 7'niz varsa, bu sizin için açıklığa kavuşacaktır.



1

İki ana seçenek vardır:

.NET> = 4.0 Dinamik LINQ :

  1. System.Linq.Dynamic kullanarak ekleyin; tepede.
  2. Kullanım: var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

NuGet ile de edinebilirsiniz .

.NET <4.0 Uzantı Yöntemleri :

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.