Entity Framework | GroupBy and Sum - linq

I have a simple table which only has 3 columns { id, DateTime, NumberOfCoils }, I need to get the results of the query grouped together by the DateTime with the sum of the NumberOfCoils on that particular date.
return testData.MOCK_DATA.Select(x => new { x.NumberOfCoils, x.DateTime })
.AsEnumerable()
.Where(x => x.DateTime> DateTime.Now.AddDays(-numberOfDays))
.Select(x => new { formattedDate = x.DateTime.ToString("yyyy-MM-dd"), x.NumberOfCoils })
.OrderBy(x => x.formattedDate)
.ToList();
I've looked for solutions here but I couldn't figure out how to solve it. I think that part of what's making it hard for me to find a solution is because I have to do some formatting as well.
I'm pretty new to using EF so I apologize if this is a bad question.

Try this:
return testData.MOCK_DATA.Select(x => new { x.NumberOfCoils, x.DateTime })
.AsEnumerable()
.Where(x => x.DateTime> DateTime.Now.AddDays(-numberOfDays))
.GroupBy(x => x.DateTime)
.Sum(x => x.NumberOfCoils);

You need to actually use GroupBy:
return testData.MOCK_DATA.Where(x => x.DateTime > DateTime.Now.AddDays(-numberOfDays))
.GroupBy(d => d.DateTime)
.Select(dg => new { formattedDate = dg.Key.ToString("yyyy-MM-dd"), NumberOfCoils = dg.Sum(d => d.NumberOfCoils) })
.OrderBy(x => x.formattedDate)
.ToList();

Related

Linq most efficient top results

I'm wondered I have a table with IDs and a version and a remove field. I d like to return the the top 20 records grouped by ID and for ech ID take only the highest version unless remove is set then ignore removed records.
Then return a descending record set.
There are a few ways todo it with Linq but I wonder is there a most efficient way, are there patterns to avoid?.
...
.OrderByDescending(x=>x.id)
.GroupBy(x=>x.id)
.SelectMany(y=>y.Where(x=>x.Version == y.Max(y=>y.Version)))
.Where(x=>x.Remove=false)
.Take(20)
One of then possible workarounds when using EF Core. I'm calling it workaround because with SQL and Window functions we can create more effective query.
var itemsQuery = ctx.SomeTable
.Where(x => x.Remove = false);
var query =
from d in itemsQuery.Select(d => new { d.id }).Distinct()
from x in itemsQuery.Where(x => d.Id == x.Id)
.OrderByDescending(x => x.Version)
.Take(1)
select x;
query = query.Take(20);
Similar queries when using EF Core 6:
var query = ctx.SomeTable
.Where(x => x.Remove = false)
.GroupBy(x => x.Id)
.Take(20)
.SelectMany(g => g.OrderByDescending(x => x.Version).Take(1));
var query = ctx.SomeTable
.Where(x => x.Remove = false)
.GroupBy(x => x.Id)
.Select(g => g.OrderByDescending(x => x.Version).First());
.Take(20);

Entity Framework Core: using navigation properties instead of joins

Recently I added Navigation Properties to a EF Core project that didn't have any FK in order to use Include instead of Join in as many queries as possible, but I've found that either the sintax doesn't suit my needs in pretty much all the queries (requires to split into multiple queries) or I'm missing something so I'll put an example, and I want to know, using Navigation Properties how would you get all the Stops with its Line list for a specific dayType?:
Stop
stopId
Point
pointId
stopId?
routeId
Route
routeId
serviceDetailId
ServiceDetail
serviceDetailId
serviceHeaderId
ServiceHeaderDay
serviceHeaderDayId
serviceHeaderId
dayType
ServiceHeader
serviceHeaderId
lineId
Line
lineId
The current working query using Join that I would like to translate using Include:
var query = (await context.Stop
.Join(
context.Point,
(stop) => stop.stopId,
(point) => point.stopId,
(stop, point) => new { Stop = stop, Point = point })
.Join(
context.Route,
(join) => join.Point.routeId,
(route) => route.routeId,
(join, route) => new { Stop = join.Stop, Route = route })
.Join(
context.ServiceDetail,
(join) => join.Route.routeId,
(serviceDetail) => serviceDetail.routeId,
(join, serviceDetail) => new { Stop = join.Stop, ServiceDetail = serviceDetail })
.Join(
context.ServiceHeader,
(join) => join.ServiceDetail.serviceHeaderId,
(serviceHeader) => serviceHeader.serviceHeaderId,
(join, serviceHeader) => new { Stop = join.Stop, ServiceHeader = serviceHeader })
.Join(
context.ServiceHeaderDay,
(join) => join.ServiceHeader.serviceHeaderId,
(serviceHeaderDay) => serviceHeaderDay.serviceHeaderId,
(join, serviceHeaderDay) => new { Stop = join.Stop, ServiceHeader = join.ServiceHeader, ServiceHeaderDay = serviceHeaderDay })
.Join(
context.Line,
(join) => join.ServiceHeader.lineId,
(line) => line.lineId,
(join, line) => new { Stop = join.Stop, ServiceHeaderDay = join.ServiceHeaderDay, Line = line })
.Where(e => e.ServiceHeaderDay.DayType == "L")
.Select(e => new { Stop = e.Stop, Line = e.Line })
.Distinct();
.ToListAsync())
// The query ends here, this next step is just grouping by Stops and inserting each Line list into them.
.GroupBy(e => e.Stop.stopId)
.Select(e =>
{
var stop = e.First().Stop;
stop.Lines = e.Select(e => e.Line).ToList();
return stop;
})
One of the failed attemps made using Include:
context.Stop
.Include(e => e.Points)
.ThenInclude(e => e.Route)
.ThenInclude(e => e.ServiceDetail)
.ThenInclude(e => e.ServiceHeader)
.ThenInclude(e => e.ServiceHeaderDay
Where(e => e.DayType = "L")
// Now I need to Include Line from ServiceHeader, but this is a of type ServiceHeaderDay
// and I think you can't use anonymous objects to carry all the tables you just include
// so I found people repeating the includes like this:
.Include(e => e.Point)
.ThenInclude(e => e.Route)
.ThenInclude(e => e.ServiceDetail)
.ThenInclude(e => e.ServiceHeader)
.ThenInclude(e => e.Line)
// This doesn't seem to work, but also how would be the select to get the Stops with all
// the lines for each Stop here?
.Select(e => ?)
If I understand your problem correctly, your query can be simplified a lot. Include usually is not for querying but for loading related data for modification purposes.
var query = context.Stop
.Where(s => s.Points.Any(p => p.Route.ServiceDetail.ServiceHeader.ServiceHeaderDay.DayType = 'L'))
.Select(s => new
{
Stop = s,
Lines = s.Points.Where(p => p.Route.ServiceDetail.ServiceHeader.ServiceHeaderDay.DayType = 'L')
.Select(p => p.Route.ServiceDetail.ServiceHeader.Line)
.ToList()
});

Using GroupBy and show different Sums based on conditions - LINQ Lambda Expressions

I have a table with 3 columns.
And I would like to present a table with this structure:
Can someone show me how to do this with Lambda expressions?
So far I've only gotten the result if I only wanted to show one column:
var sum_data = _context.HechosFinanza
.Where(x => x.Product.Sale_Type == "Cash Sale")
.GroupBy(x => x.Product.Product_Name)
.Select(x => Product { Tienda = x.Key, Total = x.Sum(s =>
s.sales_amount) });
I don't know if something like this may be possible (no idea really, just trying to figure it out):
var sum_data = _context.HechosFinanza
// I remove there where condition from here
.GroupBy(x => x.Product.Product_Name)
// And I add the where condition in each sum
.Select(x => Product { Tienda = x.Key,
TotalCash = x.Sum(s => s.sales_amount).Where(s => s.Product.Sale_Type == "Cash Sale"),
TotalCredit = x.Sum(s => s.sales_amount).Where(s.Product.Sale_Type == "Credit Sale")
});
Uhm, well. It turns out I was really close.
Just had to put the 'Where' statement before.
Answer:
var sum_data = _context.HechosFinanza
// I remove there where condition from here
.GroupBy(x => x.Product.Product_Name)
// And I add the where condition in each sum
.Select(x => Product { Tienda = x.Key,
TotalCash = x.Where(s => s.Product.Sale_Type == "Cash Sale").Sum(s => s.sales_amount),
TotalCredit = x.Where(s.Product.Sale_Type == "Credit Sale") .Sum(s => s.sales_amount)
});
And done.

Linq to return all rows with the same (lowest) value

I have a linq query that currently returns 1 product detail row for each product.
return ProductDetailView
.Select() // this is a method in repository
.Where(x => x.MaxTerm >= days)
.GroupBy(x => new { ID = x.ProductID })
.Select(g => g.OrderBy(x => x.MaxTerm).First())
.OrderBy(x => (x.CostPer100 + x.FeesExcluded))
.ToList();
This line orders the list by the product cost
.OrderBy(x => (x.CostPer100 + x.FeesExcluded))
What I'd like to do is select all the rows that have the same value as the lowest cost - in other words take all rows until the value changes.
Is this even possible in 1 query?
If you group by cost, then order the groups, the first group will contain all the rows that have the lowest cost:
return ProductDetailView
.Select() // this is a method in repository
.Where(x => x.MaxTerm >= days)
.GroupBy(x => x.ProductID)
.Select(g => g.OrderBy(x => x.MaxTerm).FirstOrDefault())
.GroupBy(x => (x.CostPer100 + x.FeesExcluded))
.OrderBy(g => g.Key)
.FirstOrDefault()
.ToList();
But as Jeff Mercado suggested, it's probably more efficient to get the lowest cost, then do another query using that as a filter. Something like this:
var min = ProductDetailView
.Select() // this is a method in repository
.Where(x => x.MaxTerm >= days)
.GroupBy(x => x.ProductID)
.Select(g => g.OrderBy(x => x.MaxTerm).FirstOrDefault())
.Min(x => (x.CostPer100 + x.FeesExcluded));
return ProductDetailView
.Select() // this is a method in repository
.Where(x => x.MaxTerm >= days && x.CostPer100 + x.FeesExcluded == min)
.GroupBy(x => x.ProductID)
.Select(g => g.OrderBy(x => x.MaxTerm).FirstOrDefault())
.ToList();

LINQ: How to Sum over Grouping using method style linq?

In the code below I'd like to do a sum for OBFY and CFY separately within each grouping in deptGroup. Anyone have any ideas, I've tried a coupla things but can't get it. Thanks!!
var deptGroup = prodCostsTotals.AsQueryable()
.Select(r => dt.Rows.Add(new object[]
{
Convert.ToInt32(r.Field<string>("Proc")),
r.Field<string>("UnitL"),
r.Field<Decimal?>("OBFY"),
r.Field<Decimal?>("CFY")
})).GroupBy(g => g.Field<string>("Proc"));
It seems to me you are overusing DataRows. If you had your data in a nice class, you could write it like this:
data.GroupBy(x => x.Proc)
.Select(g => new
{
Proc = g.Key,
OBFYSum = g.Sum(x => x.OBFY),
CFYSum = g.Sum(x => x.CFY)
})
But if you really want to start with a DataTable and add the intermediate result to another DataTable, you could do it like this:
prodCostsTotals.AsQueryable()
.Select(r => dt.Rows.Add(new object[]
{
Convert.ToInt32(r.Field<string>("Proc")),
r.Field<string>("UnitL"),
r.Field<Decimal?>("OBFY"),
r.Field<Decimal?>("CFY")
}))
.GroupBy(g => g.Field<string>("Proc"))
.Select(g => new
{
Proc = g.Key,
OBFYSum = g.Sum(x => x.Field<Decimal?>("OBFY")),
CFYSum = g.Sum(x => x.Field<Decimal?>("CFY"))
})
Note that I have no idea whether your use of AsQueryable() will have any effect on the first part of this query, but it certainly won't have any effect on the grouping.

Resources