How can I merge two LINQ queries into one query? - c#

I have the following SQL query:
select
[Event2].EventType AS 'Event type',
SUM (1) AS 'Number of events',
AVG(DATEDIFF(Second, [Event1].CreationDate, [Event2].CreationDate)) AS 'Time'
from [Event] as [Event1]
join [Event] as [Event2] on [Event1].Id = [Event2].ParentId
group by [Event2].EventTypeId;
for which I found two LINQ queries.
// This query brings the event types and the differences between the events.
var rows = from event1 in _eventRepository.AsQueryable()
join event2 in _eventRepository.AsQueryable() on event1.Id equals event2.ParentId
select new
{
EventId = event2.EventId,
TimeInSeconds = DbFunctions.DiffSeconds(event1.CreationDate, event2.CreationDate)
};
// This query groups the rows before by Event type.
var groups = (from item in rows
group item by item.EventTypeId into g
select new EventModel
{
EventTypeId = g.Key,
NumberOfEvents = g.Sum(x => 1),
Time = (int) g.Average(x => x.TimeInSeconds)
}).ToList();
I have to merge this queries into a single one.
The result must contain three elements: Event Type, The number of events, The Average of the time elapsed between the creation date of first event and the creation date of the second event.

You should just add the grouping statement to the first query (instead of the select):
var result = (from event1 in _eventRepository
join event2 in _eventRepository on event1.Id equals event2.ParentId
group new {
EventId = event2.EventId,
TimeInSeconds = DbFunctions.DiffSeconds(event1.CreationDate, event2.CreationDate)
}
by event2.EventTypeId into g
select new EventModel {
EventTypeId = g.Key,
NumberOfEvents = g.Count(),
Time = (int) g.Average(x => x.TimeInSeconds)
}).ToList();
You can also not specify the fields in the grouping but just in the select statement but left it as that is the original

Related

Using LINQ Expressions to Multiple Left Join and use ISNULL Functionality

I'm building a Step Tracking web app at work. I'm working with the latest EF Core. There are three tables I'm interacting with:
wg: WellnessGroup (WellnessGroupId, Name)
wgu: WellnessGroupUser (Look up table: WellnessGroupId, EmployeeId)
wsl: WellnessStepsLog (EmployeeId, StepCount)
What I want is to get all of the WellnessGroups and the total step amount for that group. If there are no steps attached to that group yet, I would like for the NULL value to be 0. I have this SQL statement which gives me the desired data:
SELECT wg.Name, SUM(ISNULL(wsl.StepCount, 0)) AS steps
FROM dbo.WellnessGroup AS wg
LEFT JOIN dbo.WellnessGroupUser AS wgu
ON wgu.WellnessGroupId = wg.Id
LEFT JOIN dbo.WellnessStepsLog AS wsl
ON wsl.EmployeeId = wgu.AzureAdUserId
GROUP BY wg.Name
ORDER BY steps DESC;
And I have managed to throw 2 LINQ expressions together on my controller which is giving me only the WellnessGroups that have steps associated with them and is not giving me the WellnessGroup data if there are no steps:
var query = _dbContext.WellnessGroupUser
.Include(x => x.WellnessGroup)
.Join(_dbContext.WellnessStepsLog, group =>
group.AzureAdUserId, steps => steps.EmployeeId,
(group, steps) => new
{
Steps = steps.StepCount,
Date = steps.TrackedDate,
Group = group.WellnessGroup.Name
}).Where(x => x.Date >= yearToDate).Where(x => x.Date <= endDate);
var stepsByGroup = query
.GroupBy(x => x.Group)
.Select(s => new
{
Group = s.Key,
Date = s.Max(x => x.Date),
Steps = s.Sum(x => x.Steps)
});
One way is to query all WellnessGroups and build the sum inside as a second subquery. Like this:
var query =
db.WellnessGroup.Select(wg => new {
wg.WellnessGroupId,
sum = (int?) wg.WellnessGroupUser
.Sum(wgu => wgu.Employee.WellnessStepsLog.Sum(wsl => wsl.StepCount))
});
Note, that the cast to (int?) is important. Otherwise sum is assumed to be int, which causes an InvalidOperationException if there is no sum for a row.
Another way is to build all the sums first. And then do the outer join with the WellnessGroups:
// sum up all stepcounts
var q1 =
from wgu in db.WellnessGroupUser
from wsl in db.WellnessStepsLog
where wgu.EmployeeId == wsl.EmployeeId
group wsl.StepCount by wgu.WellnessGroupId
into g
select new {WellnessGroupId = g.Key, Sum = g.Sum()};
// join with all WellnessGroups
var q2 =
from wg in db.WellnessGroup
join s in q1 on wg.WellnessGroupId equals s.WellnessGroupId into sj
from sum in sj.DefaultIfEmpty()
select new {wg, sum = (int?) sum.Sum};
EDIT:
Since the OP later asked in the comments how to group by more then one field. Here is an example which groups by WellnessGroup.WellnessGroupId and the month of WellnessStepsLog.TrackedDate. There can be more then one field in GroupBy by placing them in a new { ... }. So the first query creates a line per possible WellnessGroup / Month combination. The second query performs the outer join with WellnessGroup just as before:
var q1 =
from wgu in db.WellnessGroupUser
from wsl in db.WellnessStepsLog
where wgu.EmployeeId == wsl.EmployeeId
group wsl.StepCount by new { wgu.WellnessGroupId, wsl.TrackedDate.Month }
into g
select new {g.Key.WellnessGroupId, g.Key.Month, Sum = g.Sum()};
// join with all WellnessGroups
var q2 =
from wg in db.WellnessGroup
join s in q1 on wg.WellnessGroupId equals s.WellnessGroupId into sj
from sum in sj.DefaultIfEmpty()
select new {wg.WellnessGroupId, Month = (int?) sum.Month, Sum = (int?) sum.Sum};

LINQ Count returning 1 instead of zero for an empty group

I've got this SQL query:
SELECT oy.ownerId, oy.Year, COUNT(doc.Id) as docCount FROM aavabruf.owneryears oy
left join vastdocuments doc
on oy.ownerId = doc.Ownerid and oy.Year = doc.Year
group by oy.ownerid, oy.year
order by docCount
It shows docCount as ZERO for the OwnerId, Year pairs that have no document match in the vastdocuments table.
I tried to do the same with LINQ using the suggested left outer join solution:
from oy in OwnerYears
join doc in VaStDocuments on new {oy.OwnerId, oy.Year} equals new {doc.OwnerId , doc.Year} into docS
from docIfNull in docS.DefaultIfEmpty()
group oy by new {oy.OwnerId, oy.Year} into g
orderby g.Count() ascending
select new { OwnerId = g.Key.OwnerId, Year = g.Key.Year, docCount = g.Count()}
However, for the OwnerId, Year groups that are not present in the VastDocuments table I get docCount as ONE, not ZERO. If I remove the
from docIfNull in docS.DefaultIfEmpty()
line the "empty" groups will not be shown at all.
How can i get the Count as zero just as it is in the SQL query? I tried the following:
Count = docIfNull == null ? 0 : g.Count()
however in this case I get an error:
The name 'docIfNull' does not exist in the current context
The simplest approach is to count non-null values:
g.Count(x => x != null)
I'd suggest moving the ordering after the select so that you can avoid repeating yourself:
select new { g.Key.OwnerId, g.Key.Year, DocCount = g.Count(x => x != null) } into result
orderby result.DocCount
select result
However, I note that currently you're not using docIfNull at all at the moment... so I suspect your join isn't really doing what you want it to. Perhaps you should be using
group docIfNull by new { oy.OwnerId, oy.Year } into g
?
SQL COUNT function ignores the NULL values, while LINQ Count function w/o predicate counts everything, including nulls.
You can get the same result in LINQ by using the predicate version of Count like this (note the group docIfNull so the g elements will be of the same type as docIfNull):
from oy in OwnerYears
join doc in VaStDocuments on new { oy.OwnerId, oy.Year } equals new { doc.OwnerId, doc.Year } into docS
from docIfNull in docS.DefaultIfEmpty()
group docIfNull by new { oy.OwnerId, oy.Year } into g
let docCount = g.Count(doc => doc != null)
orderby docCount ascending
select new { OwnerId = g.Key.OwnerId, Year = g.Key.Year, docCount = docCount }
(the let clause is just to reuse the expression in orderby and select).
However in LINQ you have another option - in case the (OwnerId, Year) combination inside OwnerYears is unique as it seems, instead of left outer join pattern followed by group by and Count filtering nulls you could use simple group join operator with regular Count call:
from oy in OwnerYears
join doc in VaStDocuments on new { oy.OwnerId, oy.Year } equals new { doc.OwnerId, doc.Year } into docs
let docCount = docs.Count()
orderby docCount ascending
select new { OwnerId = oy.OwnerId, Year = oy.Year, docCount = docCount }

IGrouping does not contain a definition for

I've been looking at other threads here to learn how to do a GroupBy in linq. I am following the EXACT syntax that has worked for others, but, it's not working.
Here's the query:
var results = from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.ContactID, // <--- Error here
Amount = amount
};
pending is a List<T> that contains, among other columns, ContactID and Amount, but those are the only two I care about for this query.
The trouble is, inside the the select new, the g. won't give me any of the columns inside the original list, pending. And when I try, I get:
IGrouping <int, LeadPurchases> does not contain a definition for ContactID, and no extension method blah blah blah...
This is the SQL I am trying to emulate:
SELECT
lp.PurchasedFromContactID,
SUM (lp.Amount)
FROM
LeadPurchases lp
GROUP BY
lp.PurchasedFromContactID
You are grouping on the basis of ContactID, so it should be the Key for the result, So you have to use g.Key instead of g.ContactID; Which means the query should be like the following:
from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.Key,
Amount = amount
};
updates :
If you want to perform grouping based on more than one column then the GroupBy clause will be like this:
group p by new
{
p.ContactID,
p.Field2,
p.Field3
}into g
select new PaymentItemModel()
{
ContactID = g.Key.ContactID,
anotherField = g.Key.Field2,
nextField = g.Key.Field3
};

How to return an object with the number of grouped rows plus the group by field value?

I'm on C#, .NET 4, and using LINQ I need to group all rows in a table, due to a fixed field, returning that groupby field value and the number of each rows grouped thanks to that clause.
The code I wrote is:
var result = (from p in db.MyPersons
group p by new { p.IDPerson } into g
select new
{
IDPerson = g.Key.IDPerson,
Counter = g.Sum(p => p.IDPerson)
});
but it returns IDPerson on both IDPerson and Counter field in the new generated object.
What's the mistake? It seems Sum doesn't sum?
If you want the number of rows in the group use Count instead: (note you don't need an anonymous type either)
var result = (from p in db.MyPersons
group p by p.IDPerson into g
select new
{
IDPerson = g.Key,
Counter = g.Count()
});
Sum would add up all of the IDPerson values in the group - which would return the same value as IDPerson if there were only one item in the group.
If you want to group by more than one column, you can do this:
var result = (from p in db.MyPersons
group p by new{ p.IDPerson, p.Surname} into g
select new
{
IDPerson = g.Key,
Surname=g.Surname,
Counter = g.Count()
});

LINQ Query with GROUP and SUM

Please help me to get my head around querying using LINQ with a GROUP and SUM.
// Query the database
IEnumerable<BestSeller> best_sellers = from bs in (db.MYDATABASE).Take(25)
where bs.COMPANY == "MY COMPANY"
group bs by bs.PRODCODE into g
orderby g.Sum(g.MQTY)
select new BestSeller()
{
product_code = ,
product_description = ,
total_quantity =
};
I wish to:
Take the top 25 items from db.MYDATABASE
Group all the results by bs.PRODCODE
Order it by the sum total for each bs.PRODCODE
Where the company is "MY COMPANY"
Then pipe the data in to my BestSeller() objects
I'm confused, because as soon as I add my group in to the mix, my bs variable becomes useless.
I'm confused, because as soon as I add my group in to the mix, my bs variable becomes useless.
Yes, because you no longer have a single item - you're now processing a sequence of groups of items. You can get at first item for each group, which I assume would be a valid way of getting at the description?
var query = from bs in db.MYDATABASE.Take(25)
where bs.COMPANY == "MY COMPANY"
group bs by bs.PRODCODE into g
orderby g.Sum(x => x.MQTY)
select new BestSeller
{
product_code = g.Key,
product_description = g.First().DESCRIPTION,
total_quantity = g.Sum(x => x.MQTY)
};
Note that without specifying an ordering, "the top 25 items from db.MYDATABASE" makes no sense. "Top" in what way? You may well want:
from bs in db.MYDATABASE.OrderByDescending(x => x.Price).Take(25)
or something similar. Note that if none of those have a company of "MY COMPANY" you'll end up with no results...
Or if you want the top 25 bestsellers, you want the "take" part at the very end:
var query = from bs in db.MYDATABASE
where bs.COMPANY == "MY COMPANY"
group bs by bs.PRODCODE into g
orderby g.Sum(x => x.MQTY) descending
select new BestSeller
{
product_code = g.Key,
product_description = g.First().DESCRIPTION,
total_quantity = g.Sum(x => x.MQTY)
};
var top25 = query.Take(25);

Categories

Resources