Query on a many to one and then returning the parents - linq

I have entity Person which have ONE entity Car. The cars entities have multiple properties and one of this is color. I want to get all the persons that have a car with all the colors that is selected in a colorlist.
If person had many cars i should be able to write it like
PersonQuery.Include(c => c.Cars).Where(q=>q.Cars.Any(colors=>ListOfColors.Contains(colors.Color)));
and I want to replace Any with Where, but thats is not correct(!?). What do I miss?

If your expectation is to filter the Cars returned with each applicable Person to only the cars that match the filtered colours, then EF6 does not support this. Your query will return Persons and their cars where that person has a matching colour, but it will return all cars for that person.
One side note: when using Linq expressions, the choice of identifier should either just be a placeholder value (I.e. "x") or reflect the items being queried, not what you are querying for. For instance when writing PersonQuery.Where(c => ... you are querying against "Persons" not "Cars", so "p" would be a less confusing identifier than "c". Expanded out we would have .Where(person => person.Cars.Any(car => ...))
In EF Core 5 there is support for filtered includes:
var persons = context.Persons
.Include(p => p.Cars
.Where(c => ListOfColors.Contains(c.Color))
.Where(p => p.Cars.Any(c=>ListOfColors.Contains(c.Color)))
.ToList();
In EF 6, you would be better off selecting the Cars, then you can build the applicable Person list:
var cars = context.Cars
.Include(c => c.Person)
.Where(c => ListOfColors.Contains(c.Color))
.ToList();
var persons = cars.Select(c => c.Person).ToList();
However, a caveat to this approach is that it should only be done with a virgin DbContext, meaning a DbContext that has not loaded any other data. If for any reason the DbContext is already tracking another car of another colour for one of the applicable person records, that car would automatically be associated with the returned Person so you could easily find yourself with the odd car that doesn't match.
If you are running a DbContext scoped to the request and want to be 100% safe, you should consider projecting the results rather than dealing with the entities directly:
var results = context.Persons
.Where(p => p.Cars.Any(c => ListOfColors.Contains(c.Color)))
.Select(p => new PersonViewModel
{
PersonId = p.Id,
Name = p.Name,
MatchingCars = p.Cars
.Where(c => ListOfColors.Contains(c.Color))
.Select(c => new CarViewModel
{
CarId = c.Id,
Brand = c.Brand,
Model = c.Model,
// ...
}).ToList()
}.ToList();
This ensures that the data returned will always only be the matching coloured cars.

Related

Select two lists as one list in ASP.NET Core linq

I am trying to create a query with ASP.NET Core EF Core and Linq that would give me a List of users based on two different lists, something like this:
return await _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.Where(u => u.Id == userID)
.Select(u => new List<User>()
{
u.PropertyTenantLeases.Select(ptl => ptl.Tenant).ToList()
u.PropertyOwners.Select(po => po.Owner).ToList()
}).FirstOrDefaultAsync();
The tables that are used in this query are connected in the following way:
Everything is fine with this query except for the Select, with the Select I am trying to achieve that it returns a list of all the tenants in the PropertyTenantLeases table which is a junction table togheter with all the Owners form the PropertyOwners junction table (both Tenant and Owner are IdentityUser classes. When I right this query like this I get the following error:
The best overloeaded Add method 'List<User>.Add(User)' for the collection initializer has some invalid arguments
and also
Argument 1: cannot convert from 'System.Collections.Generic.List<RosyMasterDBManagement.Models.User>' to 'RosyMasterDBManagement.Models.User'
Joining two list is called a union in Linq -- I believe that is what you want:
note: I still can't test this since you gave a picture of the data model instead of the code that would allow me to be certain of how to implement. expect the fields to be named incorrectly etc.
var ownerlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.ToList();
var tenantlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.ThenInclude(po => po.Tenant)
.ToList();
return ownerlist.Union(tenantlist);
I don't believe you need await() since ToList() forces it to not be lazy. But I could be wrong about that.

LINQ to Entities - query across relationships and filter

I am having a very difficult time with querying a set of related entities with LINQ and Lambda expressions.
I have four entities that are related as such ...
Vehicles 1:n VehicleTypes n:1 Prices 1:n CustomerTypes
I am trying to obtain a list of Prices for a given Vehicle and CustomerType. For example I would like to obtain all the Prices for a Ford Mustang (VehicleTypeId = 2). In those Prices I would like included the CustomerType (Government, Commercial, Retail) that the Price pertains to.
I thought I might be able to do the following ...
Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Select(v => v.Id == 2)
However I get this error ...
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<bool>' to 'bool'
I cannot seem to create a Where condition where I can filter the Id of the Vehicle to be purchased yet also include the CustomerType in the results.
EDIT: Just wanted to note that I have included using System.Data.Entity so I have access to the type safe Include extension
If you need the prices to that kind of vehicle and an specific customer type, you can filter as follows:
var prices= Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Any(v => v.Id == 2)// With this condition you make sure that Mustang belong to this set of vehicles
&& p.CustomerTypes.Type=="Commercial");
But in case you want to filter the vehicles in the result, you are going to need to project your query to an anonymous type or a DTO:
var query= Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Any(v => v.Id == 2)
&& p.CustomerTypes.Type=="Commercial")
.Select(p=>new {CustomerType=p.CustomerTypes.Type,
Vehicles=p.VehicleTypes.Vehicles.Where(v => v.Id == 2)});

NotSupportedException for LINQ Queries

I am trying to get a list of a database table called oracleTimeCards whose employee id equals to the employeeID in employees list. Here is what I wrote:
LandornetSQLEntities db = new LandornetSQLEntities();
List<OracleEmployee> employees = db.OracleEmployees.Where(e => e.Office.Contains(officeName) && e.IsActive == true).Distinct().ToList();
var oracleTimeCards = db.OracleTimecards.Where(c => employees.Any(e => c.PersonID == e.PersonID)).ToList();
Anyone has any idea?
I'm going to assume you're using Entity Framework here. You can't embed calls to arbitrary LINQ extension methods inside your predicate, since EF might not know how to translate these to SQL.
Assuming you want to find all the timecards for the employees you found in your first query, you have two options. The simplest is to have a navigation property on your Employee class, named let's say TimeCards, that points to a collection of time card records for the given employee. Here's how that would work:
var oracleTimeCards = employees
.SelectMany(e => e.TimeCards)
.ToList();
If you don't want to do this for whatever reason, you can create an array of employee IDs by evaluating your first query, and use this to filter the second:
var empIDs = employees
.Select(e => e.PersonID)
.ToArray();
var oracleTimeCards = db.OracleTimecards
.Where(tc => empIDs.Contains(tc.PersonID))
.ToList();

Nhibernate Fetch/FetchMany/ThenFetch duplicate results

I'm quering my database. The structure looks like below
Country 1..M CountryLocales
1
..
M
Cities 1..M CityLocales
So, each Country has multiple locales, each City has multiple locales and a Country has multiple cities.
I try to retrieve a city from the database. I want to prefetch the Citylocales, the country and the country locales.
To do this I perform this query:
City city = Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.Country)
.ThenFetch(c => c.CountryLocales)
.FetchMany(x => x.CityLocales)
.AsEnumerable()
.FirstOrDefault();
For some reason I now get both duplicate records for the CountryLocales and for the CityLocales (both twice)
How can I fix this?
You should look into the Future method. This allows you to perform many feteches without bumping into this issue. Your current query is returning a Cartesian Product which you don't want. Using the Future method you can perform multiple queries each using one Fetch whose results are then aggregated together thus resulting in the desired result.
It might go something like this:
var result = Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.Country)
.ToFuture();
Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.CountryLocales)
.ToFuture();
Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.CityLocales)
.ToFuture();
// execute query
City city = result.AsEnumerable().FirstOrDefault();
Take a look at this answer for more information: Multiple Fetches in linq to nhibernate

How to order a collection and its subcollection using LINQ?

I have a collection of Employees, and each employee has a collection of Responsibilities. I would like to output a list of the employees sorted by Name and output their Responsibilities, sorted by Title.
So, it should be outputted like this:
Jane Jones
Responsibilities:
Responsibility A
Responsibility B
Mike Smith
Responsibilities:
Responsibility A
Responsibility C
To get the initial collection I use:
var employees = _context.Employees.OrderBy(e => e.Name);
but I can't seem to figure out how to order the Responsibilities subcollection as well.
I'm using MVC and my View receives a strongly typed Employee collection so I don't want to build and return an anonymous type.
You could do something like:
var employees = _context.Employees.OrderBy(e => e.Name);
employees.ToList().ForEach(e => e.Responsibilities = e.Responsibilities.OrderBy(r => r.Name));
1st approach: (is only suitable for LINQ to Objects with Responsibilities defined as IList)
You could copy into a new collection of Employees but this means that you need to re-copy each field into new employee, something like this:
var employees = from e in _context.Employees
orderby e.Name
select new Employee
{
Name = e.Name,
...re-assign other properties...
Responsibilities = e.Responsibilities.OrderBy(r => r.Name).ToList()
};
2nd approach:
Using the DataLoadOptions. Basically, you create a DataLoadOptions object which specifies how to order a particular relationship, so in your case:
var options = new DataLoadOptions();
options.AssociateWith<Employees>(e => e.Responsibilities.OrderBy(r => r.Name));
_context.LoadOptions = options;
var employees = _context.Employees.OrderBy(e => e.Name);
The Responsibilities will be ordered automatically.

Resources