LINQ Query Syntax: Group By and OrderBy and take one row - linq

Following query will group the records in the Assignment table, added within the given day, by the person assisted and the completed time.
(from l in Assignments where ((DateTime)l.AddedLocalTime).Date == TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local).Date
group l by new {l.FkAssistedBy, l.CompletedTime} into groups
orderby groups.Key.CompletedTime descending
select new { user = groups.Key.FkAssistedBy, groups.Key.CompletedTime })
Here, if a particular user has completed multiple assignments within the day, there will be multiple records per the user. But what I want is to group by the user and get ONLY the last completed record per user. How do I use the Take(1) or First() on the above query to get the desired result? I need to output the assistedBy and completedTime fields.

This query should work with LINQ to Objects:
(
from l in Assignments
where ((DateTime)l.AddedLocalTime).Date == TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local).Date
group l by new { l.FkAssistedBy } into g
select new
{
user = g.Key.FkAssistedBy,
CompletedRecord = g.OrderbyDescending(x = c.CompletedTime).FirstOrDefault()
}
)

Related

How to write SQL translateable linq code that groups by one property and returns distinct list

I want to change code below to be sql translateable because now i get exception.
Basicallly i want list of customers from certain localisation and there could be more than one customer with the same CustomerNumber so i want to take the one that was most recently added.
In other words - distinct list of customers from localisation where "distinct algorithm" works by taking the most recently added customer if there is conflict.
The code below works only if it is client side. I could move Group By and Select after ToListAsync but i want to avoid taking unnecessary data from database (there is include which includes list that is pretty big for every customer).
var someData = await DbContext.Set<Customer>()
.Where(o => o.Metadata.Localisation == localisation)
.Include(nameof(Customer.SomeLongList))
.GroupBy(x => x.CustomerNumber)
.Select(gr => gr.OrderByDescending(x => x.Metadata.DateAdded).FirstOrDefault())
.ToListAsync();
Short answer:
No way. GroupBy has limitation: after grouping only Key and Aggregation result can be selected. And you are trying to select SomeLongList and full entity Customer.
Best answer:
It can be done by the SQL and ROW_NUMBER Window function but without SomeLongList
Workaround:
It is because it is not effective
var groupingQuery =
from c in DbContext.Set<Customer>()
group c by new { c.CustomerNumber } into g
select new
{
g.Key.CustomerNumber,
DateAdded = g.Max(x => x.DateAdded)
};
var query =
from c in DbContext.Set<Customer>().Include(x => x.SomeLongList)
join g in groupingQuery on new { c.CustomerNumber, c.DateAdded } equals
new { g.CustomerNumber, g.DateAdded }
select c;
var result = await query.ToListAsync();

Linq: Select Most Recent Record of Each Group

I want to get the latest record of each group from a SQL Server table using Linq.
Table Example:
I want to get this result:
My Linq query returns one record for each company, but it doesn't return the most recent ones:
var query = from p in db.Payments
where p.Status == false
&& DateTime.Compare(DateTime.Now, p.NextPaymentDate.Value) == 1
group p by p.CompanyID into op
select op.OrderByDescending(nd => nd.NextPaymentDate.Value).FirstOrDefault();
What am i missing here? Why isn't the NextPaymentDate being ordered correctly?
!!UPDATE!!
My query is working as expected. After analysing #Gilang and #JonSkeet comments i ran further tests and found that i wasn't getting the intended results due to a column that wasn't being updated.
var query = from p in db.Payments
where p.Status == false
group p by p.CompanyID into op
select new {
CompanyID = op.Key,
NextPaymentDate = op.Max(x => x.NextPaymentDate),
Status = false
};
The reason your query is not being ordered correctly is that your query does not do proper grouping. You did correctly grouping by CompanyID, but then you have to retrieve the maximum NextPaymentDate by calling aggregate function.
Status can be assigned false because it is already filtered by Where clause in the early clauses.

Multiple Counts within a single query

I want a list of counts for some of my data (count the number of open.closed tasks etc), I want to get all counts inside 1 query, so I am not sure what I do with my linq statement below...
_user is an object that returns info about the current loggedon user
_repo is am object that returns an IQueryable of whichever table I want to select
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID equals
instructions.InstructionID
group new {task, instructions}
by new
{
task
}
into g
select new
{
TotalEveryone = g.Count(),
TotalMine = g.Count(),
TotalOpen = g.Count(x => x.task.IsOpen),
TotalClosed = g.Count(c => !c.task.IsOpen)
}).SingleOrDefault();
Do I convert my object to single or default? The exception I am getting is, this sequence contains more than one element
Note: I want overall stats, not for each task, but for all tasks - not sure how to get that?
You need to dump everything into a single group, and use a regular Single. I am not sure if LINQ-to-SQL would be able to translate it correctly, but it's definitely worth a try.
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID == instructions.InstructionID
group task by 1 /* <<=== All tasks go into one group */ into g select new {
TotalEveryone = task.Count(),
TotalMine = task.Count(), // <<=== You probably need a condition here
TotalOpen = task.Count(x => x.task.IsOpen),
TotalClosed = task.Count(c => !c.task.IsOpen)
}).Single();
From MSDN
Returns the only element of a sequence, or a default value if the
sequence is empty; this method throws an exception if there is more
than one element in the sequence.
You need to use FirstOrDefault. SingleOrDefault is designed for collections that contains exactly 1 element (or none).

Combining LINQ queries to get unique records

I'm trying to figure out a way of combining these 2 LINQ queries. The first one creates a list of holiday customers and the second one de-dupes the list based on the email address.
Whats the most succinct way of combining them?
var allNonBuyers = (from a in allCustomers
where !(from q in db.Quotes
where q.CreationDate > DateTime.Now.AddMinutes(duration)
join p in db.Passengers on q.QuoteGUID equals p.QuoteGUID
where q.PolicyNumber != null
select p.EmailAddress).Contains(a.EmailAddress)
select new { a.QuoteGUID, a.Title, a.FirstName, a.LastName, a.EmailAddress, a.Telephone });
var distinctNonBuyers = from buyer in allNonBuyers
group buyer by buyer.EmailAddress
into gbuyer
select gbuyer.First();
You normally don't need to - because LINQ has delayed execution, when you ask for the result it will all be combined. If this is Linq-To-Sql then it will be executed as one statement (if possible) against the database.

Stuck on a subquery that is grouping, in Linq`

I have some Linq code and it's working fine. It's a query that has a subquery in the Where clause. This subquery is doing a groupby. Works great.
The problem is that I don't know how to grab one of the results from the subquery out of the subquery into the parent.
Frst, here's the code. After that, I'll expplain what piece of data i'm wanting to extract.
var results = (from a in db.tblProducts
where (from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
}).Take(3)
.Any(
r =>
r.IdProductCode_Alpha== a.IdProductCode_Alpha&&
r.IdProductCode_Beta== a.IdProductCode_Beta&&
r.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName}).ToList();
Ok. I've changed some field and tables names to protect the innocent. :)
See this last line :-
select new {a.IdProduct, a.FullName}).ToList();
I wish to include in that the ReviewCount (from the subquery). I'm jus not sure how.
To help understand the problem, this is what the data looks like.
Sub Query
IdProductCode_Alpha = 1, IdProductCode_Beta = 2, IdProductCode_Gamma = 3, ReviewCount = 10
... row 2 ...
... row 3 ...
Parent Query
IdProduct = 69, FullName = 'Jon Skeet's Wonder Balm'
So the subquery grabs the actual data i need. The parent query determines the correct product, based on the subquery filters.
EDIT 1: Schema
tblProducts
IdProductCode
FullName
ProductFirstName
tblReviews (each product has zero to many reviews)
IdProduct
IdProductCode_Alpha (can be null)
IdProductCode_Beta (can be null)
IdProductCode_Gamma (can be null)
IdPerson
So i'm trying to find the top 3 products a person has done reviews on.
The linq works perfectly... except i just don't know how to include the COUNT in the parent query (ie. pull that result from the subquery).
Cheers :)
Got it myself. Take note of the double from at the start of the query, then the Any() being replaced by a Where() clause.
var results = (from a in db.tblProducts
from g in (
from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
})
.Take(3)
Where(g.IdProductCode_Alpha== a.IdProductCode_Alpha&&
g.IdProductCode_Beta== a.IdProductCode_Beta&&
g.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName, g.ReviewCount}).ToList();
While I don't understand LINQ completely, but wouldn't the JOIN work?
I know my answer doesn't help but it looks like you need a JOIN with the inner table(?).
I agree with shahkalpesh, both about the schema and the join.
You should be able to refactor...
r => r.IdProductCode_Alpha == a.IdProductCode_Alpha &&
r.IdProductCode_Beta == a.IdProductCode_Beta &&
r.IdProductCode_Gamma == a.IdProductCode_Gamma
into an inner join with tblProducts.

Resources