Lambda left join with rows - c#

I have two tables. A table called Order and a table called OrderRows.
An Order can have zero or more OrderRows.
I want to query all Orders and do a Sum for all OrderRows that belong to that Order.
I do that like this:
var model = await _dbContext.Orders
.Join(_dbContext.OrderRows, o => o.Id, or => or.OrderId, (o, or) => new {o, or})
.GroupBy(x => new
{
x.o.Id,
x.o.Name
})
.Select(g => new CustomDto
{
Id = g.Key.Id,
Name = g.Key.Name,
TotalPrice = g.Sum(x => x.wkr.Price)
}).ToListAsync();
This works fine for all Orders that have OrderRows. However, some Orders don't have any OrderRows (yet).
Right now the Orders that don't have any OrderRows, are not included in the result.
In those cases I still want to have them in my result, but with a TotalPrice of 0.
What do I have to change in my Lambda query?

You can use simple Select without grouping. Just calculate TotalPrice as sub-query:
var model = await _dbContext.Orders.Select(o => new CustomDto
{
Id = o.Id,
Name = o.Name,
TotalPrice = _dbContext.OrderRows.Where(or => or.OrderId == o.Id).Sum(or => or.wkr.Price)
}).ToListAsync();
I've not tested it, but hope that idea is clear

Related

How do I include attribute in Entity Framework Group By result

Let's say I have a table of locations with location ID and location name. And let's say I want to get the revenues for each location (in this simple scenario I might not even need GroupBy - but please assume that I do!)
var revenues = await _context.SaleTransaction.GroupBy(s => s.LocationId)
.Select(x => new LocationDTO {
LocationId = x.Key,
LocationName = ???
Revenues = x.Sum(i => i.Amount)
}).ToListAsync();
I tried to cheat
LocationName = x.Select(i => i.Location.LocationName).First()
since all location names for this ID are the same. But EF can't translate First() unless I use AsEnumerable() and bring the whole sales table into application memory.
Or I can traverse the result the second time:
foreach(var revenue in revenues) {
revenue.LocationName = _context.Location.Find(revenue.LocationId).LocationName;
}
Given that the number of locations is fixed (and relatively small), it may be the best approach. Still, neither going to DB for every location O(n) nor pulling the whole location list into memory doesn't sit well. Maybe there is a way to assign LocationName (and some other attributes) as part of GroupBy statement.
I am using EF Core 5; or if something is coming in EF Core 6 - that would work as well.
From what I can briefly see is that you need a linq join query in order to join the searches. With EF linq query it means those won't be loaded into memory until they are used so it would solve the problem with loading the whole table.
You could write something like:
var revenues = await _context.SaleTransactions.Join(_context.Locations, s => s.LocationId, l => l.Id, (s, l) => new {LocationId = s.LocationId, LocationName = l.LocationName, Revenues = s.Sum(i => i.Amount)});
I will link the whole fiddle with the mock of your possible model
https://dotnetfiddle.net/BGJmjj
You can group by more than one value. eg;
var revenues = await _context.SaleTransaction
.GroupBy(s => new {
s.LocationId,
s.Location.Name
})
.Select(x => new LocationDTO {
LocationId = x.Key.LocationId,
LocationName = x.Key.Name,
Revenues = x.Sum(i => i.Amount)
}).ToListAsync();
Though it seems like you are calculating a total per location, in which case you can build your query around locations instead.
var revenues = await _context.Location
.Select(x => new LocationDTO {
LocationId = x.Id,
LocationName = x.Name,
Revenues = x.SaleTransactions.Sum(i => i.Amount)
}).ToListAsync();
var revenues = await _context.Location
.Select(x => new LocationDTO {
LocationId = x.Id,
LocationName = x.Name,
Revenues = x.SaleTransactions.Sum(i => i.Amount)
}).ToListAsync();
there is example:
.NetFiddle

Order by and group by and sum using SQL

What I am trying to do is get the top 10 most sold Vegetables by grouping them by an Id passed by parameter in a function and ordering them by the sum of their Quantity. I don't know how to use SUM or (total) quite yet but I thought I'd post it here seeking help. If you need me offering you anything else I will be ready.
This is my code:
TheVegLinQDataContext db = new TheVegLinQDataContext();
var query =db.OrderDetails.GroupBy(p => p.VegID)
.Select(g => g.OrderByDescending(p => p.Quantity)
.FirstOrDefault()).Take(10);
And this is an image of my database diagram
Group orders by Vegetable ID, then from each group select data you want and total quantity:
var query = db.OrderDetails
.GroupBy(od => od.VegID)
.Select(g => new {
VegID = g.Key,
Vegetable = g.First().Vegetable, // if you have navigation property
Total = g.Sum(od => od.Quantity)
})
.OrderByDescending(x => x.Total)
.Select(x => x.Vegetable) // remove if you want totals
.Take(10);
Since this is not clear that you are passing what type of id as function parameter, I'm assuming you are passing orderId as parameter.
First apply where conditions then group the result set after that order by Total sold Quantity then apply Take
LINQ query
var result = (from a in orderdetails
where a.OrderId == orderId //apply where condition as per your needs
group a by new { a.VegId } into group1
select new
{
group1.Key.VegId,
TotalQuantity = group1.Sum(x => x.Quantity),
group1.FirstOrDefault().Vegitable
}).OrderByDescending(a => a.TotalQuantity).Take(10);
Lamda (Method) Syntax
var result1 = orderdetails
//.Where(a => a.OrderId == 1) or just remove where if you don't need to filter
.GroupBy(x => x.VegId)
.Select(x => new
{
VegId = x.Key,
x.FirstOrDefault().Vegitable,
TotalQuantity = x.Sum(a => a.Quantity)
}).OrderByDescending(x => x.TotalQuantity).Take(10);

Linq Group By with Count and Key Name

I'm attempting to get the company name from a table in which I create the new anonymous type after a group. The query works if I comment out the "CompanyName" Line
db.tbl
.GroupBy(a => a.ID)
.Select(b => new {
// This line is where I need help, I want to grab the company name
CompanyName = b.GroupBy(x=>x.CustomerName).ToString(),
CustomerId = (int) b.Key,
TotalQuotes = b.Count()
})
You don't need to Group each list again.
As I suppose the CustomerName will be the same for all the entities part of a Group, you can simply take the first entity and extract from it the CustomerName:
db.tbl
.GroupBy(a => a.ID)
.Select(c => new {
CompanyName = c.First().CustomerName,
CustomerId = (int) c.Key,
TotalQuotes = c.Count()
});

Group by generates a huge query

I have the following linq query that produces a very big SQL.
var visits = _db.Visits.AsNoTracking().GroupBy(x => x.City)
.Select(group => new
{
City = group.Key.Code,
CityName = group.Key.Name,
Count = group.Count()
}).OrderByDescending(x => x.Count);
Because the Visits table has lots of columns. But I am interested in just one column in that Visits table, which I am grouping by.
So this hits performance and query is slow.
How can i make it faster?
Select out just the data you need first so the data set you are working with will be smaller. This should reduce the amount of columns and the size of the query.
var visits = _db.Visits.AsNoTracking()
.Select(c=> new // reduce the initial data set
{
City= c.City,
Code = c.Code,
Name = c.Name
})
.GroupBy(x => x.City)
.Select(group => new // build results
{
City = group.Key.Code,
CityName = group.Key.Name,
Count = group.Count()
})
.OrderByDescending(x => x.Count);

Use Linq to return first result for each category

I have a class (ApplicationHistory) with 3 properties:
ApplicantId, ProviderId, ApplicationDate
I return the data from the database into a list, however this contains duplicate ApplicantId/ProviderId keys.
I want to supress the list so that the list only contains the the earliest Application Date for each ApplicantId/ProviderId.
The example below is where I'm currently at, but I'm not sure how to ensure the earliest date is returned.
var supressed = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.First();
All advice appreciated.
Recall that each group formed by the GroupBy call is an IGrouping<ApplicationHistory>, which implements IEnumerable<ApplicationHistory>. Read more about IGrouping here. You can order those and pick the first one:
var oldestPerGroup = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).FirstOrDefault());
You are selecting first group. Instead select first item from each group:
var supressed = history
.GroupBy(x => new {
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).First());
Or query syntax (btw you don't need to specify names for anonymous object properties in this case):
var supressed = from h in history
group h by new {
h.ApplicantId,
h.ProviderId
} into g
select g.OrderBy(x => x.ApplicationDate).First();

Categories