I have a problem getting last row in a each group. I am using Linq query to retrieve the groups.
Here is my LINQ query.
return View(db.tblMsgs.OrderByDescending(a => a.Id)
.GroupBy(a => new { a.Sender, a.Receiver }).Select(x => x.FirstOrDefault())
.Where(a => a.Receiver == username).ToList());
using FirstOfDefault() I am getting first row in a group.
Using LastOrDefault() I am getting run time exception.
That's what I run into too, in some time now.
After a little bit of research I found out that only way that works as it must is to reverse the list that you want the get the last item of and get the first item.
The reason behind this is that SQL languages does not have a statement as SELECT BOTTOM but SELECT TOP. Hence our LastOrDefault query could not be translated into SQL.
The possible way of doing so is to OrderByDescending method.
return View(db.tblMsgs.OrderByDescending(a => a.Id)
.GroupBy(a => new { a.Sender, a.Receiver }).Select(x => x.OrderByDescending(y => y.SomeAttribute).FirstOrDefault())
.Where(a => a.Receiver == username).ToList());
Edit:
Only thing you should be choosy about is the column to order by. It can be the id field if it is an auto incremented number value, or an add date of the row(better be generated by server or it can cause problems).
Related
Here is my query. UserName column is citext, having non-unique index on it.
var logs = Db.Logs
.GroupBy(x => x.UserName)
.ToDictionary(g => g.Key, g => g.Max(m => m.Time));
I'm getting an exception:
System.ArgumentException: An item with the same key has already been
added
I don't understand, how is it possible to be any dupe keys in resultant dictionary, if grouped values can't have dupes by design?
P.S.
I have an update on this. While investigating, I've looked into generated sql for GroupBy statement and got stuck (see below). Why group by interpreted as order by instead??
SELECT x."Time", x."UserName"
FROM "UserAccessLogs" AS x
ORDER BY x."UserName" NULLS FIRST
Well, seems that is a kind of 'documented behavior'. Grouping for further manipulation (like ToDictionary f.e.) requires anonymous type representation before. I rewrote my query as
var logs = Db.Logs
.GroupBy(x => x.UserName)
.Select(g => new { UserName = g.Key, Time = g.Max(m => m.Time)})
.ToDictionary(d => d.UserName, d => d.Time);
and it works as expected. That's why EF was interpreting my query wrong and that's why I was getting a 'dupe keys' error.
In this code:
var dbrepayments = _context.Repayments.Include("Loan").Include("Loan.Borrower").Include("Loan.LoanProduct")
.Where(c => c.PaidOn == null && c.DateOfRepayment <= today)
.GroupBy(c => c.Loan.Id, (key, g) => g.OrderByDescending(c => c.Id).FirstOrDefault())
.OrderBy(c => c.DateOfRepayment);
_context is ApplicationDbContext type that I am using to get results from database using Code-First approach.
The problem is when I try to iterate through dbrepayments and get the value of Loan, Loan.Borrower, and Loan.LoanProduct objects they are showing as null. But when I remove GroupBy, these objects are returned correctly.
I'd wager the issue here is the element selector in your GroupBy statement:
(key, g) => g.OrderByDescending(c => c.Id).FirstOrDefault()
This didn't make a lot of sense when I first read it. You are taking repayments grouped by loan, but then trying to select just the last repayment for each loan? Followed by ordering those first repayments by date.
I believe this will give you the results you're looking for with the eager loaded relationships:
var dbrepayments = _context.Repayments.Include("Loan").Include("Loan.Borrower").Include("Loan.LoanProduct")
.Where(c => c.PaidOn == null && c.DateOfRepayment <= today)
.GroupBy(c => c.Loan.Id)
.Select(c => c.OrderByDescending(x => x.Id).FirstOrDefault())
.OrderBy(c => c.DateOfRepayment);
GroupBy will respect Include but if you are using a select expression, that overrides it. You cannot add Include inside the selector as that is working with IEnumerable of the expected results. Instead, group the results by loan as expected, but then Select from the results to get the latest repayment. This will give you a list of the latest repayments that you can then order.
I have a datatable that I am returning to the UI layer.
I have multiple tables with the same FirstId value. A few may have a value in teh FieldOne. I only want to group the records where FieldOne is null.
I tried the following LINQ statement with .Where and .Groupby but the .Where removes all the records with values in FieldOne and then do the GroupBy. In the UI grid, the records with FieldOne values are missing. I want to only group the records with empty FieldOne values and still have the records with FieldOne values. Thanks.
MyDataAsEnumerable()
.Where(f => f.Field<string>("FieldOne") == null)
.GroupBy(r => new { pp1 = r.Field<int>("FirstId") })
.Select(g => g.First())
.CopyToDataTable();
You could make an artifical grouping key:
.GroupBy(
r => new { pp1 = f.Field<string>("FieldOne") == null ? -1 : r.Field<int>("FirstId") })
Here, I used -1 as a hack to create a separate group. Make sure this int value is not in use. You could also solve this precisely but hopefully this is OK.
I have the following simple table with ID, ContactId and Comment.
I want to select records and GroupBy contactId. I used this LINQ extension method statement:
Mains.GroupBy(l => l.ContactID)
.Select(g => g.FirstOrDefault())
.ToList()
It returns record 1 and 4. How can I use LINQ to get the ContactID with the highest ID? (i.e. return 3 and 6)
You can order you items
Mains.GroupBy(l => l.ContactID)
.Select(g=>g.OrderByDescending(c=>c.ID).FirstOrDefault())
.ToList()
Use OrderByDescending on the items in the group:
Mains.GroupBy(l => l.ContactID)
.Select(g => g.OrderByDescending(l => l.ID).First())
.ToList();
Also, there is no need for FirstOrDefault when selecting an item from the group; a group will always have at least one item so you can use First() safely.
Perhaps selecting with Max instead of OrderByDescending could result into improving of performance (I'm not sure how it's made inside so it needs to be tested):
var grouped = Mains.GroupBy(l => l.ContactID);
var ids = grouped.Select(g => g.Max(x => x.Id));
var result = grouped.Where(g => ids.Contains(g.Id));
As I assume it could result into a query that will take MAX and then do SELECT * FROM ... WHERE id IN ({max ids here}) which could be significantly faster than OrderByDescending.
Feel free to correct me if I'm not right.
OrderByDescending
Mains.GroupBy(l => l.ContactID)
.Select(g=>g.OrderByDescending(c=>c.ID).FirstOrDefault())
.ToList()
is your best solution
It orders by the highest ID descending, which is pretty obvious from the name.
You could use MoreLinq like this for a shorter solution:
Main.OrderByDescending(i => i.Id).DistinctBy(l => l.ContactID).ToList();
I have 2 LINQ Queries here, i just want to know which of these query is proper and fast to use.
Sample I
var GetUSer = (from UserItem in dbs.users
where UserItem.UserID == UserID
select new User(UserItem))
.OrderBy(item => item.FirstName)
.Skip(0)
.Take(10)
.ToList();
Sample II
var GetUSer = (from UserITem in dbs.user
.Where(item => item.UserID == UserID)
.OrderBy(item => item.FirstName)
.Skip(0)
.Take(10)
.AsEnumerable()
select new User(UserItem)).ToList();
Although they are both working well, i just want to know which is the best.
The Second one is better, the first 1 does a select then does filtering, meaning it has to get the data from the database first to turn it into a User object, then it filters.
The second one will do the query on the DB side, then turn it into a User object
The first one can be fixed by moving the select till just before the ToList()
Between those two, I would prefer the first (for readability, you'd need to switch some things around if you want the whole query to execute in the database). If they both work, it's up to you though.
Personally, I don't like mixing query syntax with lambda syntax if I don't have to, and I prefer lambda. I would write it something like:
var GetUsers = db.user
.Where(u => u.UserID == UserID)
.OrderBy(u => u.FirstName)
.Take(10)
.Select(u => new User(u))
.ToList();
This uses a single syntax, queries as much as possible in the database, and leaves out any superfluous calls.