LINQ To Entities ne reconnaît pas la méthode Last. Vraiment?

144

Dans cette requête:

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters
        .OrderBy(p => p.ServerStatus.ServerDateTime)
        .GroupBy(p => p.RawName)
        .Select(p => p.Last());
}

J'ai dû le changer pour que ça marche

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters
        .OrderByDescending(p => p.ServerStatus.ServerDateTime)
        .GroupBy(p => p.RawName)
        .Select(p => p.FirstOrDefault());
}

Je ne pouvais même pas utiliser p.First(), pour refléter la première requête.

Pourquoi y a-t-il des limitations de base dans ce qui est autrement un système ORM aussi robuste?

bevacqua
la source
stockez votre objet IEnumrable dans une nouvelle variable, puis retournez variable.last (). ça va marcher.
Ali.Rashidi

Réponses:

220

Cette limitation se résume au fait qu'il doit finalement traduire cette requête en SQL et SQL a un SELECT TOP(en T-SQL) mais pas un SELECT BOTTOM(rien de tel).

Il existe cependant un moyen simple de contourner le problème, il suffit de commander en ordre décroissant , puis de faire un First(), ce que vous avez fait.

EDIT: D'autres fournisseurs auront éventuellement des implémentations différentes de SELECT TOP 1, sur Oracle, ce serait probablement quelque chose de plus commeWHERE ROWNUM = 1

ÉDITER:

Une autre alternative moins efficace - je ne recommande PAS cela! - consiste à appeler .ToList()vos données avant .Last(), ce qui exécutera immédiatement l'expression LINQ To Entities qui a été construite jusqu'à ce point, puis votre .Last () fonctionnera, car à ce stade, le .Last()est effectivement exécuté dans le contexte d'un LINQ to Objects Expression à la place. (Et comme vous l'avez souligné, cela pourrait ramener des milliers d'enregistrements et gaspiller des charges d'objets matérialisant le processeur qui ne seront jamais utilisés)

Encore une fois, je ne recommanderais pas de faire cette seconde, mais cela aide à illustrer la différence entre où et quand l'expression LINQ est exécutée.

Neil Fenwick
la source
et comment LINQ To SQL gère-t-il ce scénario?
bevacqua
@Neil oui, je sais que je peux appeler ToList, mais je préfère ne pas récupérer des milliers d'enregistrements de la base de données juste pour les filtrer jusqu'à cinq enregistrements
bevacqua
2
Si vous savez que votre requête va renvoyer de petits résultats, l'appel ToListn'est pas si mal.
Justin Skiles
35

Au lieu de Last(), essayez ceci:

model.OrderByDescending(o => o.Id).FirstOrDefault();
Azarsa
la source
14

Remplacer Last()par un sélecteur LinqOrderByDescending(x => x.ID).Take(1).Single()

Quelque chose comme ça fonctionnerait si vous préférez le faire dans Linq:

public static IEnumerable<IServerOnlineCharacter> GetUpdated()
{
    var context = DataContext.GetDataContext();
    return context.ServerOnlineCharacters.OrderBy(p => p.ServerStatus.ServerDateTime).GroupBy(p => p.RawName).Select(p => p.OrderByDescending(x => x.Id).Take(1).Single());
}
Ema.H
la source
1
Y a-t-il une raison d'utiliser .Take (1) .Single () au lieu de .FirstOrDefault ()?
Tot Zam
2
@TotZam Le remplacement valide serait .First () dans ce cas, puisque Single () lève une exception si le nombre d'éléments n'est pas exactement 1.
MEMark
0

Encore une autre façon d'obtenir le dernier élément sans OrderByDescending et de charger toutes les entités:

dbSet
    .Where(f => f.Id == dbSet.Max(f2 => f2.Id))
    .FirstOrDefault();
Stas Boyarincev
la source
0

C'est parce que LINQ to Entities (et les bases de données en général) ne prend pas en charge toutes les méthodes LINQ (voir ici pour plus de détails: http://msdn.microsoft.com/en-us/library/bb738550.aspx )

Ce dont vous avez besoin ici, c'est d'ordonner vos données de manière à ce que le "dernier" enregistrement devienne le "premier" et vous pouvez ensuite utiliser FirstOrDefault. Notez que les bases de données n'ont généralement pas de concepts tels que "premier" et "dernier", ce n'est pas comme si l'enregistrement le plus récemment inséré sera "dernier" dans le tableau.

Cette méthode peut résoudre votre problème

db.databaseTable.OrderByDescending(obj => obj.Id).FirstOrDefault();
Mohammad Hassani
la source
-2

L'ajout d'une seule fonction AsEnumerable()avant la fonction Select a fonctionné pour moi.
Exemple:

return context.ServerOnlineCharacters
    .OrderByDescending(p => p.ServerStatus.ServerDateTime)
    .GroupBy(p => p.RawName).AsEnumerable()
    .Select(p => p.FirstOrDefault());

Réf: https://www.codeproject.com/Questions/1005274/LINQ-to-Entities-does-not-recognize-the-method-Sys

Artem Levitin
la source
Il est conseillé d'incorporer le code de travail du lien dans votre réponse. Les réponses aux liens uniquement attireront une attention négative. Veuillez fournir une réponse complète en ajoutant le code que vous avez trouvé pour vous aider et résoudre le problème. Cela résout un problème de liens qui ne fonctionnent pas en raison d'erreurs 404 à l'avenir.
Studocwho
1
a ajouté l'exemple à ma réponse
Artem Levitin
L'inconvénient de cette réponse est qu'elle apportera tous les résultats avant le côté serveur "AsEnumerable", puis sélectionnera le premier. Cela pourrait être très indésirable. (J'ai eu une situation comme celle-ci où les résultats prenaient plus de 20 secondes en raison du fait que plus de 20 000 enregistrements étaient apportés côté serveur, une fois que je l'ai déplacé côté DB, les résultats sont retournés en moins d'une seconde)
TChadwick