linq to entities group by sub-query - linq

I have the following code:
var statements = db.statement
.OrderByDescending(d => d.id)
.Take(5)
.AsEnumerable()
.Select(d => new Statements
{
Accounts = d.statement_entry.AsEnumerable()
.GroupBy(b => b.currency)
.Select(b =>
new Account
{
In = b.Where(l => l.amount > 0).Sum(l => l.amount),
Out = b.Where(l => l.amount < 0).Sum(l => l.amount),
Balance = b.Sum(l => l.amount),
Currency = b.Key
}).OrderBy(b => b.Currency),
UploadedDate = d.uploaded_date,
Id = d.id
})
.ToList();
Is there a way that I could do it without the AsEnumerable()? From what I understand the AsEnumberable will cause a query to take place for each of the statements returned.
Or is there a better way to refactor the code?

You understand wrongly. AsEnumerable will make the query execute on the local (client) machine.
This
statements = db.statement
.OrderByDescending(d => d.id)
.Take(5)
will be executed on the (SQL) server,
the remaining part on the client
Why are you puttin the AsEnumerable? I think the query should work even without (and it would do everything server-side)
The only thing is that after the OrderBy(b => b.Currency) you should put a .ToList() so that the .Select(b => new Account is materialized and cached.

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.

Nhibernate query for Any condition

I have table structure like
Product {List<Cost> Costs}
Cost{List<Invoice> Invoices, Product product}
Invoice{bool isIncluded}
Need a query to get all Products which has any Cost for which none of invoice is included (isIncluded=false for all)
I tried something like:
Product pro= null;
Product p = null;
var costQuery = QueryOver.Of<Cost>()
.JoinAlias(c => c.Product, () => p)
.Where(() => p.Id == pro.Id)
.WhereNot(c=>c.Invoices.Any(i=>i.IsIncluded))
.Select(c => c.Id);
var query = CurrentSession.QueryOver<Product>(() => pro)
.WithSubquery.WhereExists(costQuery);
Use of 'Any' in query errors out:
Unrecognised method call: System.Linq.Enumerable:Boolean
Any[Invoice](System.Collections.Generic.IEnumerable1[Trigger.StageGate.Services‌​.BusinessEntities.Invoice],
System.Func2[Trigger.StageGate.Services.BusinessEntities.Invoice,System.Boolean‌​])
try:
var costQuery = QueryOver.Of<Cost>()
.JoinAlias(c => c.Product, () => p)
.Where(() => p.Id == pro.Id)
.JoinQueryOver<Invoice>(c => c.Invoices)
.WhereNot(i => i.IsIncluded)
.Select(c => c.Id);

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