web-dev-qa-db-fra.com

Paging avec LINQ pour les objets

Comment implémenteriez-vous la pagination dans une requête LINQ? Pour l’instant, je serais satisfait si la fonction sql TOP pouvait être imitée. Cependant, je suis sûr que le besoin d’un soutien complet en matière de radiomessagerie apparaît de toute façon plus tôt.

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

Vous recherchez les méthodes d'extension Skip et Take. Skip dépasse les N premiers éléments du résultat et renvoie le reste; Take renvoie les N premiers éléments du résultat, en supprimant les éléments restants.

Voir MSDN pour plus d'informations sur l'utilisation de ces méthodes: http://msdn.Microsoft.com/en-us/library/bb386988.aspx

Par exemple:

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

Utiliser Skip et Take est définitivement la voie à suivre. Si je l'implémentais, j'écrirais probablement ma propre méthode d'extension pour gérer la pagination (pour rendre le code plus lisible). L’implémentation peut bien sûr utiliser Skip et 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);
  }
}

La classe définit deux méthodes d'extension - une pour IEnumerable et une pour IQueryable, ce qui signifie que vous pouvez l'utiliser avec LINQ to Objects et LINQ to SQL (lors de l'écriture d'une requête dans la base de données, le compilateur choisira la version IQueryable.

En fonction de vos besoins en matière de pagination, vous pouvez également ajouter un comportement supplémentaire (par exemple, pour gérer des valeurs pageSize ou page négatives). Voici un exemple d'utilisation de cette méthode d'extension dans votre requête:

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

Voici mon approche performante de la pagination lorsque j'utilise LINQ avec des objets:

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

Ceci peut alors être utilisé comme suit:

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

Aucune de ces ordures Skip et Take qui sera très inefficace si vous êtes intéressé par plusieurs pages.

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

Je ne sais pas si cela aidera quelqu'un, mais je l'ai trouvé utile pour mes objectifs:

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

Pour utiliser ceci, vous auriez une requête linq et passeriez le résultat avec la taille de la page dans une boucle 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
}

Donc, cela va parcourir chaque auteur en recherchant 100 auteurs à la fois.

4
Bitfiddler

EDIT - Skip supprimé (0) car ce n'est pas nécessaire

var queryResult = (from o in objects where ...
                      select new
                      {
                          A = o.a,
                          B = o.b
                      }
                  ).Take(10);
4
Jack Marchetti
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);

Batchsize sera évidemment un entier. Cela tire parti du fait que les entiers suppriment simplement les décimales.

Je plaisante à moitié avec cette réponse, mais elle fera ce que vous voulez, et comme elle est différée, vous ne risquez pas une pénalité de performances importante

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

Cette solution ne concerne pas LinqToEntities, je ne sais même pas si cela pourrait en faire une bonne requête.

2
Todd A. Stedel

Similaire à La réponse de Lukazoid J'ai créé une extension pour 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);
                    }
                }
            }

C'est utile si Skip ou Take ne sont pas supportés.

1
Michael Freidgeim

Il y a deux options principales:

.NET> = 4.0LINQ dynamique :

  1. Ajouter en utilisant System.Linq.Dynamic; au sommet.
  2. Utilisez: var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

Vous pouvez également l'obtenir par NuGet .

.NET <4.0Méthodes d'extension :

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

J'utilise cette méthode d'extension:

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

c’est ce que j’ai fait . Normalement, vous commencez à 1 mais dans IList vous commencez avec 0 . Donc si vous avez 152 lignes, cela signifie que vous en avez 8, mais dans IList vous n’avez que 7 . peut clarifier les choses pour vous 

0
Alen.Toma

var results = (medicineInfo.OrderBy(x=>x.id)
                       .Skip((pages -1) * 2)
                       .Take(2));

0
Debendra Dash