50 votes

Entity Framework/Linq à SQL : Sauter et prendre

Je suis juste curieux de savoir comment Skip & Take est censé fonctionner. J'obtiens les résultats que je veux voir du côté client, mais lorsque je connecte le profileur SQL d'AnjLab et que je regarde le SQL qui est exécuté, il semble qu'il demande et renvoie l'ensemble des lignes au client.

Est-il vraiment possible de renvoyer toutes les lignes, puis de les trier et de les réduire avec LINQ du côté client ?

J'ai essayé de le faire avec Entity Framework et Linq to SQL ; les deux semblent avoir le même comportement.

Je ne suis pas sûr que cela fasse une différence, mais j'utilise C# dans VWD 2010.

Une idée ?

public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
    var context = new TectonicEntities();
    totalRecords = context.Stores.Count();
    int skipRows = (page - 1) * pageSize;
    if (desc)
        return context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
    return context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
}

SQL résultant (Note : J'exclue la requête Count) :

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name], 
[Extent1].[LegalName] AS [LegalName], 
[Extent1].[YearEstablished] AS [YearEstablished], 
[Extent1].[DiskPath] AS [DiskPath], 
[Extent1].[URL] AS [URL], 
[Extent1].[SecureURL] AS [SecureURL], 
[Extent1].[UseSSL] AS [UseSSL]
FROM [dbo].[tec_Stores] AS [Extent1]

Après quelques recherches plus approfondies, j'ai découvert que le système suivant fonctionne comme je l'attendais :

public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
    var context = new TectonicEntities();
    totalRecords = context.Stores.Count();
    int skipRows = (page - 1) * pageSize;           
    var qry = from s in context.Stores orderby s.Name ascending select s;
    return qry.Skip(skipRows).Take(pageSize);           
}

SQL résultant :

SELECT TOP (3) 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name], 
[Extent1].[LegalName] AS [LegalName], 
[Extent1].[YearEstablished] AS [YearEstablished], 
[Extent1].[DiskPath] AS [DiskPath], 
[Extent1].[URL] AS [URL], 
[Extent1].[SecureURL] AS [SecureURL], 
[Extent1].[UseSSL] AS [UseSSL]
FROM ( SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[LegalName] AS [LegalName], [Extent1].[YearEstablished] AS [YearEstablished], [Extent1].[DiskPath] AS [DiskPath], [Extent1].[URL] AS [URL], [Extent1].[SecureURL] AS [SecureURL], [Extent1].[UseSSL] AS [UseSSL], row_number() OVER (ORDER BY [Extent1].[Name] ASC) AS [row_number]
    FROM [dbo].[tec_Stores] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 3
ORDER BY [Extent1].[Name] ASC

J'aime vraiment la façon dont la première option fonctionne ; Passer dans une expression lambda pour le tri. Existe-t-il un moyen d'accomplir la même chose avec la syntaxe orderby de LINQ to SQL ? J'ai essayé d'utiliser qry.OrderBy(sort).Skip(skipRows).Take(pageSize), mais cela a fini par me donner les mêmes résultats que mon premier bloc de code. Cela m'amène à penser que mes problèmes sont liés d'une manière ou d'une autre à OrderBy.

\====================================

PROBLÈME RÉSOLU

J'ai dû envelopper la fonction lambda entrante dans Expression :

Expression<Func<Store,string>> sort

0 votes

Pouvez-vous nous donner le code de la fonction de tri ?

0 votes

Bien sûr, je passe juste un lambda. Exemples : x => x.Name, x => x.LegalName, x => x.YearEstablished.ToString()

0 votes

Je commence à penser que je devrais simplement passer une chaîne de caractères puis utiliser une instruction switch pour définir le paramètre orderby approprié pour la requête LINQ :( La première méthode était tellement plus cool et impliquait beaucoup moins de code. Je n'arrive pas à comprendre pourquoi elle ne fonctionne pas correctement. Sans savoir exactement ce qui se passe, il semble que .OrderBy et .OrderByDescending déclenchent une récupération de la base de données, puis appliquent le tri, puis sautent et prennent. Peut-être que c'est ça, cependant... peut-être que OrderBy ne sait pas comment convertir x => x.Name dans le SQL approprié et qu'il récupère le jeu de résultats puis applique le tri et le filtrage.

42voto

Sam Points 3231

Ce qui suit fonctionne et accomplit la simplicité que je recherchais :

public IEnumerable<Store> ListStores(Expression<Func<Store, string>> sort, bool desc, int page, int pageSize, out int totalRecords)
{
    List<Store> stores = new List<Store>();
    using (var context = new TectonicEntities())
    {
        totalRecords = context.Stores.Count();
        int skipRows = (page - 1) * pageSize;
        if (desc)
            stores = context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
        else
            stores = context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
    }
    return stores;
}

La principale chose qui l'a résolu pour moi a été de changer le paramètre de tri Func en :

Expression<Func<Store, string>> sort

0 votes

J'ai eu un problème similaire. J'ai remarqué dans SQL Profiler que les requêtes étaient simplement des SELECT * FROM et j'ai remonté la piste jusqu'au fait que ce paramètre Expression<> manquait peut-être dans l'appel. Je l'ai mis à jour (j'utilisais EF4.1 pour référence) et cela a résolu mon problème.

0 votes

Quelqu'un sait-il pourquoi c'est le cas ? J'ai eu exactement le même problème. Je pensais avoir gardé toute mon expression en tant que IQueryable sans appeler ToList ou autre chose qui énumère l'expression. Est-ce que Func provoque une énumération alors que Expression ne le fait pas ?

0 votes

@BrianSweeney La raison en est, je crois, que si la requête de tri est no enveloppé dans une expression, linq est incapable de construire un arbre d'expression pour le transformer en sql. Pour cette raison, il doit énumérer la requête pour la trier avant de faire le skip / take.

8voto

Jan Jongboom Points 15148

Tant que tu ne le fais pas comme queryable.ToList().Skip(5).Take(10) il ne retournera pas le jeu d'enregistrements complet.

Prenez

Faire seulement Take(10).ToList() , est-ce qu'un SELECT TOP 10 * FROM .

Skip

Skip fonctionne un peu différemment car il n'y a pas de fonction 'LIMIT' dans TSQL. Cependant, il crée une requête SQL qui est basée sur le travail décrit dans cet article. Article de blog de ScottGu .

Si le jeu d'enregistrements entier est renvoyé, c'est probablement parce que vous effectuez une recherche de type ToList() quelque part trop tôt.

0 votes

Faire ToList() à la fin. Exemple : db.Stores.OrderBy(x => x.Name).Skip(5).Take(5).ToList()

0 votes

Oui, mais plus tôt. Est-ce qu'une autre méthode fait un ToList() sur votre jeu original.

0 votes

Le code ci-dessus ne retournera pas toutes vos données. Soit vous devez mieux regarder dans le profiler, soit vous faites ToList sur un autre endroit du code à ces données. Essayez d'exécuter le profileur en ne faisant que ce morceau de code.

3voto

Mick Points 554

Solution Entity Framework 6 ici...

http://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/

par exemple

using System.Data.Entity;
....

int skip = 5;
int take = 10;

myQuery.Skip(() => skip).Take(() => take);

1voto

Ali Yousefie Points 987

J'ai créé une extension simple :

public static IEnumerable<T> SelectPage<T, T2>(this IEnumerable<T> list, Func<T, T2> sortFunc, bool isDescending, int index, int length)
{
    List<T> result = null;
    if (isDescending)
        result = list.OrderByDescending(sortFunc).Skip(index).Take(length).ToList();
    else
        result = list.OrderBy(sortFunc).Skip(index).Take(length).ToList();
    return result;
}

Utilisation simple :

using (var context = new TransportContext())
{
    var drivers = (from x in context.Drivers where x.TransportId == trasnportId select x).SelectPage(x => x.Id, false, index, length).ToList();
}

1voto

Si vous utilisez SQL Server comme base de données

Ensuite, vous pouvez convertir

context.Users.OrderBy(u => u.Id)
.Skip(() => 10)
.Take(() => 5)
.ToList

\=>

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[UserName] AS [UserName]
FROM [dbo].[AspNetUsers] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY

référence : https://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X