Linq Where then Select on a HashSet - c#

I'm trying to get a list of products that match a certain category id. The problem I'm having is that I'm using the select clause before the where, essentially trying to filter once I've already got all the results. Usually its straight forward but since this navigation property is a HashSet its proving more tricky. My repo.GetAll() gets all the products from my database. CategoryProducts is a linking table between products and Categories, its also a navigation property on the product table
ProductRepository repo = new ProductRepository();
var products =
repo.GetAll()
.Select(c => c.CategoryProducts
.Where(p => p.CategoryId == 35));
The above just returns all my products, any help is appreciated.

Your query returns an enumerable that corresponds to all your products, each item of that enumerable is an enumerable itself, containing zero or more categories with ID of 35.
You can change your query to get only products that have category 35 in them:
var products = repo
.GetAll()
.Where(p => p.CategoryProducts.Any(c => c.CategoryId == 35));

Related

Replacing ElementAt in a LINQ query to cover multiple elements

I want to retrieve a list of Orders based on a multiple Location lists.
For context: a Group can have a multiple Locations (one-to-many) and an Order has one Location (many-to-one). I want to get all the Orders of a Group based on the Locations it has.
The following works for the first element in the list of Groups:
List<Order> orders = _context.Orders
.Where(o => groups.ElementAt(0).Locations.Contains(o.Location))
.ToList()
I want to change it such that it will not only check the first Group element, but all of them. Help is appreciated.
As groups is only a variable, you can use SelectMany to flatten the nested hierarchy before running the query:
var locations = groups.SelectMany(x => x.Locations).ToArray();
This basically changes a nested hierarchy
G1
L11
L12
G2
L21
L22
to a flat list of locations:
L11
L12
L21
L22
After that, you can use the locations in the query:
List<Order> orders = _context.Orders
.Where(o => locations.Contains(o.Location))
.ToList()
If the groups can contain duplicate locations, you could also add a Distinct after the SelectMany.
Is this what you are looking for?
var result = orders
.Where(o => groups.All(f =>
f.Locations.Contains(o.Location))).ToList();

Lambda Expression - How to provide values to where clause from an IEnumerable<Object>?

I've a supplier table filled with supplier objects, supplierID is the primary key here. I've to find all products supplied by these suppliers. Since there is a many-many relationship, I've a bridge table in between SupplierProducts, with supplierID and productID as a composite primary key.
I've used lambda function to get an IEnumerable<SupplierProducts> for a particular supplier. Now, I'd like to query the products table to find all products that are in IEnumerable<SupplierProducts>. I don't want to use a foreach() to populate the products table, but rather an 'in' like lambda expression.
I'm sure this must have been answered earlier, but unfortunately after trying for fifteen minutes, I've not been able to find a clear solution. I've looked into contains() and any() functions. Here is my code:
IEnumerable<SupplierProducts> supplierProducts = db.SupplierProducts.Where(w => w.SupplierID == supplierID).ToList();
IEnumerable<Products> products = db.Products.Where(w => w.ProductID.contains(supplierProducts.productID)).ToList();
You are pretty close: all you need to do is selecting IDs, and then using Contains, like this:
var supplierProductIds = db.SupplierProducts
.Where(w => w.SupplierID == supplierID)
.Select(p => p.productID)
.ToList(); // You could get better performance without ToList
IEnumerable<Products> products = db.Products
.Where(w => supplierProductIds.Contains(w.ProductID))
.ToList();
If you have constructed you model correctly you should have the virtual property for Products in Supplier class
public virtual ICollection<Product> Products {get; set;}
and this would have made your task easier:
db.Suppliers.Where(s => s.Id == supplierID).Select(s => s.Products)

c# linq join with projection

So in my shop app i'm allowing users to favorite items.
what i wish to do is to show the list of favorite items in the user profile page where the list is sorted on the like date in a descending order.
We have 2 tables that we need to join, one is items and the other one is favorites.
how would one join this two tables, so the result will answer this criteria:
The result will be a list of items that was favorite by this particular user.
The results will come with the list of comments for each item (each item have a list of comments).
The results will be sorted correctly.
So far i came up with this:
Items =
await _context.Favorites
.Join(
_context.Items,
f => f.ItemId,
i => i.Id,
(f, i) => new { f, i })
.Distinct()
.OrderByDescending(x => x.f.FavDate)
.Select(x => x.i)
.Skip(skip).Take(take)
.Include(c => c.ListOfComments)
.ToListAsync();
This works but does not answer the first criteria, which is that only items favorite by particular user will be returned, this returns list of items favorite by the users and not by a particular user.
I tried to add a where clause before the join (_context.Favorites.Where(f.UserVoterId.equals(profileId)) but it throws an exception.
One way to approach this is to:
include the user id as the join key and
load the data in separate steps
To select the favorite items of a specific user (profileId) you need this query:
var favorites = _context.Favorites.OrderByDescending(f => f.FavDate)
.Join(_context.Items,
fav => new { fav.ItemId, UserId = fav.UserVoterId },
item => new { ItemId = item.Id, UserId = profileId },
(fav, item) => item)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList();
And to load the comments just try one of the following (whichever works):
var itemIds = favorites.Select(f => f.Id);
var comments = _context.Comments.Where(c => itemIds.Contains(c.ItemId))
.GroupBy(c => c.ItemId)
.ToDictionary(g => g.Key, g => g.ToArray());
Or
var items = _context.Comments
.GroupJoin(favorites,
comment => comment.ItemId,
favorite => favorite.Id,
(fav, comments) => new
{
Item = fav,
Comments = comment.ToArray()
});
In the first case, the comments are added to a Dictionary<TItemId, Comment> where TItemId is the type of item.Id and to get the comments for an item you'd use
var itemComments = comments[item.Id];
which is a O(1) operation.
In the second case the items collection will have all the data you need so you'll have to project it into the structure that suits your needs.
NB I mentioned earlier whichever works because I'm not entirely sure that GroupJoin is properly translated to SQL and I'm not sure if I missed some requirements for the GroupJoin method.

Why am I getting a Compile time error in this linq query?

I am getting a compile time error when compiling the below code and I can't see why:
Relations are many to many relations
var contacts = groups_to_querry
.SelectMany(x => x.Contacts)
.Where(x => x.ID == Guid.Empty)
.SelectMany(p => p.ContactDetails)
.Where(x => x.ID == Guid.Empty)
.SelectMany(x => x.Contacts); //This line gives me a compile time error
//Error : The Type argumetns for method 'System.Linq.Enumerable.SelectMany<TSource,Tresult>
//(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,
//System.Collections.Generic.IEnumerable<TResult>>)' cannot be infrred from the usage.
//Try specifying the type arguments explicitly
The second time you call for .SelectMany(x => x.Contacts), you are currently working with a collection of ContactDetails. It is doubtful that you would be able to use SelectMany on it. You would need to use Select instead.
SelectMany is used when you want to select multiple collections of items and put them into one IEnumerable. Select is used on individual fields. Since you are working with objects of type ContactDetail (which I assume can only have one contact), you would need use Select
EDIT: Here is what you're doing in a nutshell, step by step:
groups_to_querry.SelectMany(x => x.Contacts): From all the groups that I want to query select all of their many contacts. Each group has many contacts, so put them all into a single IEnumerable collection of type Contact
.Where(x => x.ID == Guid.Empty): ...but only those Contacts with an empty ID
.SelectMany(p => p.ContactDetails): Then select all of those Contacts' many ContactDetails. Each Contact has many ContactDetails, so put them all into a single IEnumerable collection of type ContactDetail
.Where(x => x.ID == Guid.Empty): ...but only those ContactDetails with an empty ID
.SelectMany(x => x.Contacts);: Now select each of the ContactDetails' many Contacts. However, since the compiler knows that there is a one-to-many relationship between Contacts and ContactDetails (and not the other way around) that statement is not possible, and thus shows a compile error
I'm interpreting your intended query as "from multiple groups of contacts, select all contacts that have ID=Guid.Empty and also have details that all have ID=Guid.Empty".
The way your code is actually interpreted is "from all contacts that have Guid.Empty, select all details that have Guid.Empty, and from those details select all contacts". The first problem is that you end up selecting from details. This means the final SelectMany should be a Select, because x.Contacts here refers to the many-to-one relationship from details to contacts.
The second problem is that the result will contain duplicates of contacts, because the same contact is included for each details. What you should be doing instead is filtering the contacts directly based on their details collections, like this:
groups_to_query
.SelectMany(g => g.Contacts)
.Where(c => c.ID == Guid.Empty)
.Where(c => c.ContactDetails.All(d => d.ID == Guid.Empty))
Note this would also select contacts that have zero details, which is different behavior from your query, so I'm not sure if it's what you want. You could add another filter for ContactDetails.Any() if not.
Edit: Since you're using Entity Framework, the above probably won't work. You may need to select the details in a subquery and then filter in memory after it executes:
var queryResult =
groups_to_query
.SelectMany(g => g.Contacts)
.Where(c => c.ID == Guid.Empty)
.Select(c => new {
contact = c,
detailIDs = c.ContactDetails.Select(d => d.ID)
}).ToList();
var contacts =
queryResult
.Where(r => r.detailIDs.All(id => id == Guid.Empty))
.Select(r => r.contact);

join 3 tables in linq getting all entries from two tables

I have 3 tables
A project table
A product table
An update table
The product table holds different products from a project, and the update table holds updates made to various products and holds a reference to the user who did it.
Basically what I want is to have a query that returns all products (since products to projects is a many to one relation) ordered by the date they we're last updated by the user who is currently logged in.
This is my current query:
IEnumerable<ProjectProduct> list =
from joined in
(from product in db.GetTable<Product>()
join project in db.GetTable<Project>()
on product.ProjectId equals project.ID
select new { product, project })
join projectupd in db.GetTable<ProjectUpdate>()
on joined.product.ID equals projectupd.ProductID
where projectupd.CreatedBy == ParamUser
orderby projectupd.LastUpdate
select new ProjectProduct(joined.project, joined.product);
However, the result I'm getting is only the entries in the update table, and not all the existing products. I know that the "where" clause makes it only select the updates created by a specific user, so I'm on the right track, but I have tried a couple of things to make the query successful, without luck though.
Does anybody have a suggestion on how to get the desired result?
Here's an answer that's a little verbose, and it uses method-chain syntax, but I do think it does what your looking for:
var products = db.GetTable<Product>();
var projects = db.GetTable<Project>();
var projectUpdates = db.GetTable<ProjectUpdate>();
var latestProjectUpdatesForUser = projectUpdates
.Where(x => x.CreatedBy == paramUser)
.GroupBy(x => x.ProductId)
.Select(g => g.OrderByDescending(x => x.LastUpdate).First());
var list = products
.Join(
projects,
product => product.ProjectId,
project => project.Id,
(product, project) => new
{
Product = product,
Project = project,
Update = latestProjectUpdatesForUser.FirstOrDefault(u => u.ProductId == product.Id)
}
)
.OrderByDescending(x => x.Update != null ? (DateTime?)x.Update.LastUpdate : null)
.ThenBy(x => x.Project.Id)
.ThenBy(x => x.Product.Id)
.Select(x => new ProjectProduct { Project = x.Project, Product = x.Product});
It takes advantage of the fact that DateTime? is sortable and that null values end up last when using OrderByDescending.

Categories