156

In this query:

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

I had to switch it to this for it to work

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

I couldn't even use p.First(), to mirror the first query.

Why are there such basic limitations in what's otherwise such a robust ORM system?

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
bevacqua
  • 47,502
  • 56
  • 171
  • 285

6 Answers6

239

That limitation comes down to the fact that eventually it has to translate that query to SQL and SQL has a SELECT TOP (in T-SQL) but not a SELECT BOTTOM (no such thing).

There is an easy way around it though, just order descending and then do a First(), which is what you did.

EDIT: Other providers will possibly have different implementations of SELECT TOP 1, on Oracle it would probably be something more like WHERE ROWNUM = 1

EDIT:

Another less efficient alternative - I DO NOT recommend this! - is to call .ToList() on your data before .Last(), which will immediately execute the LINQ To Entities Expression that has been built up to that point, and then your .Last() will work, because at that point the .Last() is effectively executed in the context of a LINQ to Objects Expression instead. (And as you pointed out, it could bring back thousands of records and waste loads of CPU materialising objects that will never get used)

Again, I would not recommend doing this second, but it does help illustrate the difference between where and when the LINQ expression is executed.

Neil Fenwick
  • 6,106
  • 3
  • 31
  • 38
  • and how does LINQ To SQL deal with this scenario? – bevacqua Sep 03 '11 at 14:19
  • @Neil yes I do know I can call ToList, but I'd rather not retrieve thousands of records from the database just to filter them down to five records – bevacqua Sep 03 '11 at 14:25
  • 2
    If you know your query is going to return small results, calling `ToList` isn't that bad. – Justin Skiles Sep 19 '13 at 19:06
  • 2
    I suppose if you are OK with "cheating" and doing this on the .NET side, then instead of `.ToList().Last()` you should do `.AsEnumerable().Last()`. No need to grow a large `List<>` in memory if all you want is to foreach your way through the entire source to get the last item. – Jeppe Stig Nielsen Aug 02 '22 at 11:33
38

Instead of Last(), Try this:

model.OrderByDescending(o => o.Id).FirstOrDefault();
Azarsa
  • 1,278
  • 3
  • 27
  • 37
  • 1
    What if I'm using generic model and cannot reference the `Id` field in it? – Bamdad Jan 10 '21 at 07:40
  • If your data isn't sorted then `Last()` has no meaning – Schiavini Apr 14 '21 at 07:29
  • @Schiavini It can still happen. I have a database where I need the first and last entry in a query, but they are not sortable by any field so that reversal is guaranteed. – mneumann Feb 22 '22 at 09:53
14

Replace Last() by a Linq selector OrderByDescending(x => x.ID).Take(1).Single()

Something like that would be works if you prefert do it in 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
  • 2,862
  • 3
  • 28
  • 41
  • 1
    Is there any reason to use .Take(1).Single() instead of .FirstOrDefault()? – Tot Zam Jul 20 '17 at 16:26
  • 2
    @TotZam The valid replacement would be .First() in that case, since Single() throws an exception if item count is not exactly 1. – MEMark Nov 20 '17 at 18:14
0

Yet another way get last element without OrderByDescending and load all entities:

dbSet
    .Where(f => f.Id == dbSet.Max(f2 => f2.Id))
    .FirstOrDefault();
Stas Boyarincev
  • 3,690
  • 23
  • 23
0

That's because LINQ to Entities (and databases in general) does not support all the LINQ methods (see here for details: http://msdn.microsoft.com/en-us/library/bb738550.aspx)

What you need here is to order your data in such a way that the "last" record becomes "first" and then you can use FirstOrDefault. Note that databasese usually don't have such concepts as "first" and "last", it's not like the most recently inserted record will be "last" in the table.

This method can solve your problem

db.databaseTable.OrderByDescending(obj => obj.Id).FirstOrDefault();
Mohammad Hassani
  • 511
  • 6
  • 14
-2

Adding a single function AsEnumerable() before Select function worked for me.
Example:

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

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

  • It's advised to incorporate the working code from the link in to your answer. Link-only answers will attract negative attention. Please provide a full answer by adding the code you've found to help and solve the problem. This solves a problem of links not working due to 404 errors in the future. – Studocwho Jun 17 '19 at 21:45
  • 1
    added the example to my answer – Artem Levitin Jun 24 '19 at 20:10
  • The down side to this answer is that it will bring all results prior to the "AsEnumerable" server side and then select the first one. This could be very undesirable. (I had a situation like this where results were taking 20+ seconds due to the 20k+ records being brought server-side, once I moved it back DB side, results returned in less than a second) – TChadwick Sep 30 '19 at 20:31