How do I include attribute in Entity Framework Group By result - c#

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

Related

Linq group by performance

I have a list of post objects. Each post object has a car property and each car object has a brand property. I am trying to find total number of posts for a particular brand, for this I am using the following code
var grp = posts.Where(t=> !t.Car.Brand.Name.Equals("Test"))
.Select(t=> new Brand
{
BrandId = t.Car.Brand.Id,
Name = t.Car.Brand.Name,
Url = t.Car.Brand.Url,
})
.GroupBy(t => t.BrandId)
.Select(t=> new Brand
{
BrandId = t.First().BrandId,
Name = t.First().Name,
Url = t.First().Url,
Count = t.Count()
}).OrderByDescending(t=>t.Count).ToList();
This code works but it is a bit slow, any suggestions to improve performance?
Using First() on grouping result dramaticallly decrease perfromance. Up to EF Core 6 it will thtow exception that this query is not translatablke. If you want to write performant queries always think in SQL way: grouping can return only grouping keys and aggregation result, other quirks are slow, even they are translatable to the SQL.
var grp = posts
.Where(t => !t.Car.Brand.Name.Equals("Test"))
.Select(t => new Brand
{
BrandId = t.Car.Brand.Id,
Name = t.Car.Brand.Name,
Url = t.Car.Brand.Url,
})
.GroupBy(t => t)
.Select(t => new Brand
{
BrandId = t.Key.BrandId,
Name = t.Key.Name,
Url = t.Key.Url,
Count = t.Count()
})
.OrderByDescending(t => t.Count)
.ToList();

Lambda left join with rows

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

How to get better performance query result on filtering data

I have query that needs to filter large set of data by some search criteria.
The search is happening through 3 tables: Products, ProductPrimaryCodes, ProductCodes.
The large data (given there is around 2000 records, so is not that large, but is largest by the other tables data) set is in ProductCodes table.
Here is an example of what I've done.
var result = products.Where(x => x.Code.Contains(se) ||
x.ProductPrimaryCodes.Any(p => p.Code.Contains(se)) ||
x.ProductCodes.Any(p => p.Code.Contains(se)))
.Select(x => new ProductDto
{
Id = x.Id,
Name = x.Name,
InStock = x.InStock,
BrandId = (BrandType)x.BrandId,
Code = x.Code,
CategoryName = x.Category.Name,
SubCategoryName = x.SubCategory.Name,
});
The time that query executes is around 8-9 sec, so i believe is quite long for this kind of search. And just a note, without doing ProductCodes.Any(), the query executes in less than a second and retrieves result to the page.
ProductCodes table:
Id,
Code,
ProductId
Any suggestions how to get better performance of the query?
This is the solution that worked for me.
var filteredProductsByCode = products.Where(x => x.Code.Contains(se));
var filteredProducts = products.Where(x => x.ProductCodes.Any(p => p.Code.Contains(se))
|| x.ProductPrimaryCodes.Any(p => p.Code.Contains(se)));
return filteredProductsByCode.Union(filteredProducts).Select(x => new ProductDto
{
Id = x.Id,
Name = x.Name,
InStock = x.InStock,
BrandId = (BrandType)x.BrandId,
Code = x.Code,
CategoryName = x.Category.Name,
SubCategoryName = x.SubCategory.Name,
}).OrderByDescending(x => x.Id)
Clearly not the cleanest, but I will also consider introducing stored procedures for this kind of queries.

Mapping not working correctly (ASP.NET)

I need to get all Proposals that related to people
I have several tables in db. It's AspNetUsers, UserToRegion,Region,Cities, Projects and proposals.
Here is model for
AspNetUsers
https://pastebin.com/xts1Xh8m
It connecting with regions with table UserToRegions
Here is it model
https://pastebin.com/8PnBuqf1
So One Region can have several Users
Here is Region Model
https://pastebin.com/9GS9Qst7
City is related to Region
So here is model for City
https://pastebin.com/VWjT0V9h
And Project related to City
So here is Project model
https://pastebin.com/ziE3Sb9C
I tried to get data for project and proposal (Proposal related on project)
Like this on Controller
public JsonResult Index(string email)
{
var id = db.AspNetUsers.Where(x=> x.Email == email).FirstOrDefault();
string id_val = id.Id;
var proposals = db.UserToRegions.Where(x=> x.User_Id == id_val)
.Include(u => u.AspNetUser).Include(u => u.Region).Include(u=>u.Region.Cities)
.Select(x=> new {
Project = x.Region.Cities.,
WorkTime = x.WorkTime,
Quantity = x.Quantity,
Price = x.Price,
Service = x.Service.Name,
DateFrom = x.Date,
DateTo = x.Date_to,
WorkTimeTo = x.WorkTimeTo,
Id = x.Id,
EditingDate = x.CreatingDate
})
.ToList();
return Json(proposals, JsonRequestBehavior.AllowGet);
}
But in this line Project = x.Region.Cities., It cannot see Projects
Here is Diagram iа it will be easier
Where is my trouble?
UPDATE
I rewrite method like this
var proposals = db.Proposals.Where(x=> x.Project.City.Region.UserToRegions)
.Select(x=> new {
Project = x.Region.Cities.,
WorkTime = x.WorkTime,
Quantity = x.Quantity,
Price = x.Price,
Service = x.Service.Name,
DateFrom = x.Date,
DateTo = x.Date_to,
WorkTimeTo = x.WorkTimeTo,
Id = x.Id,
EditingDate = x.CreatingDate
})
.ToList();
return Json(proposals, JsonRequestBehavior.AllowGet);
}
And now, I not see UserToRegions.UserId.
Your lambda Expression is combining multiple table via join operation but you are passing only where() condition without parameter to join all other table say userid but instead join multiple table with some parameter like this
var UserInRole = db.UserProfiles.
Join(db.UsersInRoles, u => u.UserId, uir => uir.UserId,
(u, uir) => new { u, uir }).
Join(db.Roles, r => r.uir.RoleId, ro => ro.RoleId, (r, ro) => new { r, ro })
.Where(m => m.r.u.UserId == 1)
.Select (m => new AddUserToRole
{
UserName = m.r.u.UserName,
RoleName = m.ro.RoleName
});
You can refer this one to solve your issue like here
Cities on the region is a collection and as such you would need to add a .FirstOrDefault() on Cities to get to a collection of projects on a city. But that would only get you projects for the first city in the region which is probably not what you want.
If you are trying to return all the projects in a region you would be better off using explicit joins and changing your approach slightly.
Here’s a starting place for you:
http://www.dotnettricks.com/learn/linq/sql-joins-with-csharp-linq

How to avoid "select n+1" pattern in Linq

I have a query (including LinqKit) of the form:
Expression<Func<Country, DateTime, bool>> countryIndepBeforeExpr =
(ct, dt) => ct.IndependenceDate <= dt;
DateTime someDate = GetSomeDate();
var q = db.Continent.AsExpandable().Select(c =>
new
{
c.ID,
c.Name,
c.Area,
Countries = c.Countries.AsQueryable()
.Where(ct => countryIndepBeforeExpr.Invoke(ct, someDate))
.Select(ct => new {ct.ID, ct.Name, ct.IndependenceDate})
});
Now I want to iterate through q... but since the Countries property of each element is of type IQueryable, it will be lazy loaded, causing n+1 queries to be executed, which isn't very nice.
What is the correct way to write this query so that all necessary data will be fetched in a single query to the db?
EDIT
Hm, well it might have helped if I had actually run a Sql trace before asking this question. I assumed that because the inner property was of type IQueryable that it would be lazy-loaded... but after doing some actual testing, it turns out that Linq to Entities is smart enough to run the whole query at once.
Sorry to waste all your time. I would delete the question, but since it already has answers, I can't. Maybe it can serve as some kind of warning to others to test your hypothesis before assuming it to be true!
Include countries to your model when you call for continents. With something like this:
var continents = db.Continent.Include(c => c.Countries).ToArray();
Then you can make your linq operations without iQueryable object.
I think this should work (moving AsExpandable() to root of IQueryable):
var q = db.Continent
.AsExpandable()
.Select(c => new
{
c.ID,
c.Name,
c.Area,
Countries = c.Countries
.Where(ct => countryIndepBeforeExpr.Invoke(ct, someDate))
.Select(ct => new {ct.ID, ct.Name, ct.IndependenceDate})
});
If not, create two IQueryable and join them together:
var continents = db.Continents;
var countries = db.Countries
.AsExpandable()
.Where(c => countryIndepBeforeExpr.Invoke(c, someDate))
.Select(c => new { c.ID, c.Name, c.IndependenceDate });
var q = continents.GroupJoin(countries,
continent => continent.ID,
country => country.ContinentId,
(continent, countries) => new
{
continent.ID,
continent.Name,
continent.Area,
Countries = countries.Select(c => new
{
c.ID,
c.Name,
c.IndependenceDate
})
});

Categories