Reputation: 961
I have the following many-to-many relation modelled
public class Profile
{
ICollection<Category> Categories { get; set;}
// One-To-Many
ICollection<Platform> Platforms { get; set; }
}
public class Category
{
ICollection<Profile> Profiles { get; set; }
}
public class ProfileCategory
{
public int ProfileId { get; set; }
public Profile Profile { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set;}
}
I'm using ASP.NET Core MVC and have a filter view model where a filter on some attributes on profile name and it works.
Trying to filter based on category proved much harder to implement (at least the solution isn't obvious to me :)
From the web the user can select zero, one or many categories to filter on so basically what is sent to my controller is a list of category ids.
IQueryable<Profile> query = _context.Profiles.Include(p => p.Categories).Include(p => p.Platforms);
if(string.IsNullOrEmpty(search.Name))
{
query = query.Where(p => p.Name.IndexOf(search.Name StringComparison.OrdinalIgnoreCase) > 0);
}
if(search.Categories?.Any() != null)
{
query = query.SelectMany(p => p.ProfileCategories)
.Join(search.Categories, pc => pc.CategoryId, cId => cId, (pc,_) => pc.Profile);
}
From this point the Profile object is different and other navigational properties such as Platforms is null hence breaking other parts.
How can I perform the join while retaining the original instance of Profile object. I first thought that they would be the same, but I was wrong.
Upvotes: 4
Views: 3849
Reputation: 16795
Currently, EF Core JOINs are not perfect, and I recommend make two queries:
1) Select list of ProfileId
(based on category list):
var profileIds = await _context.ProfileCategory
.Where(x => categoryIds.Contains(x.CategoryId)) // filtering goes here
.Select(x => x.ProfileId)
.Distinct()
.ToListAsync();
2) Select required data based on known IDs:
var result = await _context.Profiles
.Include(p => p.Categories).Include(p => p.Platforms)
.Where(x => profileIds.Contains(x.ProfileId))
.ToListAsync();
Yes, this is two queries instead one, but two simple queries, easily optimized using indexes.
Upvotes: 7