views:

70

answers:

4

I have a many to many relationship as follows:

Products ProductID Description

ProductFeatures ProductFeatureID ProductID FeatureID

Features FeatureID Description

Any Product can have many Features.

I then come along with an iQueryable called "SearchFeatures" which contains two particular Feature objects that I want to search on.

I want to find the Products which have ALL of these Features!

E.g. something like this would be nice:

return db.Products.Where(x => x.Features.ContainsAll(SearchFeatures));

What is the cleanest way to achieve this using LINQ?

Many thanks.

+1  A: 

Something like this ought to work

public partial class Product
{
    public IEnumerable<Feature> Features
    {
        get
        {
            return ProductFeatures.SelectMany(pf => pf.Feature);
        }
    }
}

Products.Where(p => SearchFeatures.All(sf => p.Features.Count(f => f.ID == sf.ID) > 0));
David Hedlund
This was working well for me, except I failed to mention that my SearchFeatures was a local collection, so I was then getting errors. But tidy solution and still deserves a point - thanks.
Aaron
@Aaron: yes, as you can see in that last line of code, it's not really inside any context at all. perhaps the formatting is misleading, but anyhow, that call was meant to be wherever you're currently making the call, and `SearchFeatures` may be any local variable in that scope.
David Hedlund
Thanks David. Your code does look very clean, but I am still getting this error "Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator.". I just plugged it in a second time to give it another try, because I like the simplicity of this solution, but it isn't happy and does give me the error.Specifically, this is my code:SearchResults = SearchResults.Where(p => search.SearchFeatures.All(sf => p.Features().Count(f => f.PropertyFeatureID == sf.PropertyFeatureID) > 0));
Aaron
Note also I had used my own existing Features() method rather than your "public IEnumerable<Feature> Features". So I just tried to create your "public IEnumerable<Feature> Features" to see if that made a difference, but then I got some really casting/types error where it wouldn't let me create it. Any other thoughts?
Aaron
@Aaron: oh, i see, yeah, i guess it doesn't translate to LINQ-to-SQL. sorry bout that. it'd be a handy solution for LINQ-to-objects =)
David Hedlund
+2  A: 
IQueryable<Products> products = db.Products;
foreach (var feature in SearchFeatures)
{
    Feature tmpFeature = feature;
    products = products
        .Where(x=>x.ProductFeatures.Any(y=>y.FeatureID == tmpFeature.FeatureID));
}
Francisco
This is a good solution if SearchFeatures is a local collection.
David B
Francisco you absolute legend.David B very very good point, because this was exactly my situation. SearchFeatures was a local collection and thus I was getting LINQ errors with the other approaches because of that. Something about not being able to cross-query a combination of local and database/committed collections.Francisco's solution deals with this perfectly and my code is running like a dream. Many thanks to both of you.
Aaron
+1  A: 
IQueryable<Product> query = db.Products
  .Where(p => SearchFeatures
    .All(sf =>
      p.ProductFeatures.Select(pf => pf.Feature).Contains(sf)
    )
  );
David B
This solution is really elegant, I will hang onto it for cases where SearchFeatures isn't Local. Many thanks.
Aaron
+1  A: 
from item in db.Products
where item.ProductFeatures.Where(x=>featIdList.Contains(x.FeatureId)).Count() == featIdList.Count
select item

This should do it. featIdList is a list of the feature ids that you're looking for

Mike
Thanks Mike, another solution along the lines of the others and I think it would work well assuming my collection wasn't local.
Aaron
@Aaron This should work with a local collection. I've successfully used it before. Just make sure that featIdList is a list of Ids and not a list of features
Mike