web-dev-qa-db-fra.com

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

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

Je devais le changer pour que cela fonctionne

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 existe-t-il de telles limitations fondamentales dans un système ORM aussi robuste?

128
bevacqua

Cette limitation découle du fait qu’il doit finalement traduire cette requête enSQLet que SQL a un SELECT TOP (en T-SQL) mais pas un SELECT BOTTOM (rien de tel).

Cependant, il existe un moyen simple de le contourner: ordre décroissant puis effectuez une First(), ce que vous avez fait.

EDIT: D'autres fournisseurs auront probablement différentes implémentations de SELECT TOP 1, sur Oracle, cela ressemblerait probablement davantage à WHERE ROWNUM = 1

MODIFIER:

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

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

199
Neil Fenwick

Au lieu de Last(), essayez ceci:

model.OrderByDescending(o => o.Id).FirstOrDefault();
26
Azarsa

Remplacez Last() par un sélecteur Linq OrderByDescending(x => x.ID).Take(1).Single()

Quelque chose comme ça fonctionnerait si vous préférez le faire à 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());
}
13
Ema.H

En effet, 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 de classer vos données de telle sorte que le "dernier" enregistrement devienne "premier" et que vous puissiez 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 le dernier enregistrement inséré sera "dernier" dans la table.

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

db.databaseTable.OrderByDescending(obj => obj.Id).FirstOrDefault();
0
Kazem

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();
0
Stas Boyarincev