Mapping not working correctly (ASP.NET) - c#

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

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();

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

ASP.NET Core Many-to-many relationship problems, trouble with include

I want to display the Positions of Employees in Department views. At the moment I am currently displaying only Employees. How can I include employee positions? The model relationship Employees and Positions is many-to-many, the relationship Department and Employees is also many-to-many.
There were no problems when the relationship of Positions and Employees was one to many.
I was trying to add to the DivisionEmployeeViewModel property a collection public IEnumerable<EmployeePosition> EmployeePositionList { get; set; }
But I don’t know how to implement it further. I'm stumped.
Method-actions in DivisionController
public async Task<IActionResult> Details(int id)
{
var model = new DivisionEmployeeViewModel
{
DivisionEmployeeList = await _db.DivisionEmployeesModel.Include(x => x.Employee)
.Include(x => x.Division).Where(x => x.Division_Id == id).ToListAsync(),
DivisionEmployees = new DivisionEmployee()
{
Division_Id = id
},
Division = await _db.Divisions.FirstOrDefaultAsync(x => x.Id == id)
};
List<int> tempAssignedList = model.DivisionEmployeeList.Select(x => x.Employee_Id).ToList();
var tempList = await _db.Employees.Where(x => !tempAssignedList.Contains(x.Id)).ToListAsync();
model.DivisionEmployeeListDropDown = tempList.Select(x => new SelectListItem
{
Text = x.FullName,
Value = x.Id.ToString()
});
return View(model);
}
My repo https://github.com/ValencyJacob/DepartmentManagementApp-Many-to-Many/tree/master/DepartmentManagement
Sorry, typo there.. myEmployee.EmployeePositions.Select(ep => ep.Position.Name), but I don't really understand what youre trying to put in your Where.. _db.EmployeePositions is indeed fulfillling requirement "i need to get the positions of the employees". For a single employee it's var posFor123 = _db.EmployeePositions.Include(ep=> ep.Position).Where(ep => ep.EmployeeId = 123) , then you get an enumerable of EmployeePosition and you can pull the positions out, perhaps like posFor123.Select(ep => ep.Position.Name) – Caius Jard

How can I reuse a subquery inside a select expression?

In my database I have two tables Organizations and OrganizationMembers, with a 1:N relationship.
I want to express a query that returns each organization with the first and last name of the first organization owner.
My current select expression works, but it's neither efficient nor does it look right to me, since every subquery gets defined multiple times.
await dbContext.Organizations
.AsNoTracking()
.Select(x =>
{
return new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).FirstName,
OwnerLastName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).EmailAddress
};
})
.ToArrayAsync();
Is it somehow possible to summarize or reuse the subqueries, so I don't need to define them multiple times?
Note that I've already tried storing the subquery result in a variable. This doesn't work, because it requires converting the expression into a statement body, which results in a compiler error.
The subquery can be reused by introducing intermediate projection (Select), which is the equivalent of let operator in the query syntax.
For instance:
dbContext.Organizations.AsNoTracking()
// intermediate projection
.Select(x => new
{
Organization = x,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.FirstOrDefault()
})
// final projection
.Select(x => new OrganizationListItem
{
Id = x.Organization.Id,
Name = x.Organization.Name,
OwnerFirstName = Owner.FirstName,
OwnerLastName = Owner.LastName,
OwnerEmailAddress = Owner.EmailAddress
})
Note that in pre EF Core 3.0 you have to use FirstOrDefault instead of First if you want to avoid client evaluation.
Also this does not make the generated SQL query better/faster - it still contains separate inline subquery for each property included in the final select. Hence will improve readability, but not the efficiency.
That's why it's usually better to project nested object into unflattened DTO property, i.e. instead of OwnerFirstName, OwnerLastName, OwnerEmailAddress have a class with properties FirstName, LastName, EmailAddress and property let say Owner of that type in OrganizationListItem (similar to entity with reference navigation property). This way you will be able to use something like
dbContext.Organizations.AsNoTracking()
.Select(x => new
{
Id = x.Organization.Id,
Name = x.Organization.Name,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.Select(member => new OwnerInfo // the new class
{
FirstName = member.FirstName,
LastName = member.LastName,
EmailAddress = member.EmailAddress
})
.FirstOrDefault()
})
Unfortunately in pre 3.0 versions EF Core will generate N + 1 SQL queries for this LINQ query, but in 3.0+ it will generate a single and quite efficient SQL query.
How about this:
await dbContext.Organizations
.AsNoTracking()
.Select(x =>
{
var firstMember = x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner);
return new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = firstMember.FirstName,
OwnerLastName = firstMember.LastName,
OwnerEmailAddress = firstMember.EmailAddress
};
})
.ToArrayAsync();
How about doing this like
await dbContext.Organizations
.AsNoTracking()
.Select(x => new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner).FirstName,
OwnerLastName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).EmailAddress
})
.ToArrayAsync();

How can I get the best seller by product group using LINQ

I need to retrieve the name, product type and sum of the seller with the highest sales in each product type using linq and return the list to a view.
{
public ActionResult Query2()
{
// this part gets me everything into one model type
List<QueryViewModel> result = db.SaleProducts
.GroupBy(prod => prod.Type)
.SelectMany(sale => sale.Select(
row => new QueryViewModel
{
Seller = row.Sale.User.Name,
ProductType = row.Type,
Sales = (double)sale.Where(x => x.Sale.UserId == row.Sale.UserId && x.Type.Equals(row.Type)).Sum(price => price.Price)
}
)).Distinct().ToList<QueryViewModel>();
// this gives me the best per product type but i cant get the seller name
List<QueryViewModel> filter = (from res in result
group res by res.ProductType into prodGroup
select new QueryViewModel
{
ProductType = prodGroup.Key,
Sales = prodGroup.Max(x => x.Sales)
}).ToList<QueryViewModel>();
// this is really just to get the seller name at this point
List<QueryViewModel> something = (from res in result
join f in filter on res.Sales equals f.Sales
select new QueryViewModel
{
Seller = res.Seller,
ProductType = res.ProductType,
Sales = res.Sales
}).ToList<QueryViewModel>();
return View(something);
}
}
It should return a list of QueryViewModel(name, product type, total sales).
It does return that, but this seems horribly messy and I'm not understanding LINQ enough to clear this up.
Is there a better cleaner way to achieve my desired output?
The first grouping should be by type and Seller.
Something like this should get you going:
Quote_Masters.GroupBy(qm => new { qm.OrderTypeId, qm.SalesRepEmployeeId })
.Select(x => new { Name = x.Key.SalesRepEmployeeId, x.Key.OrderTypeId, Total = x.Sum(qm => qm.Total_Price) })
.OrderByDescending(x => x.Total).GroupBy(x => x.OrderTypeId).Select(x => x.FirstOrDefault())
Just change out your appropriate tables and fields.
Edit: Your select to QueryViewModel would be the new {Name = x.Key.SalesRepEmployeeId, x.Key.OrderTypeId, Total = x.Sum(qm => qm.Total_Price)}

Categories