Reputation: 22760
I took a look at this answer and it goes in part to solving my issue.
However, what I need is the following.
Given I have an object;
Product
string code
List<suitability> items
and then i have this object;
Suitability
key
value
Each Product has a variable amount of [items] and the key/value pairs differ from product to product.
When searching I am given a list of Suitability objects. I now need to search all the products that contain (all) of the supplied suitability objects.
So for example a product may have dental = true and therapies = true.
I may get a request for all products that have dental = true and therapies = false.
Upvotes: 1
Views: 996
Reputation: 10561
Using PredicateBuilder (from the authors of LinqPad) you can build up a query from a set of conditions not known at compile time. The following should be sufficient:
var predicate = PredicateBuilder.True<Product>();
foreach (Suitability criteria in searchCriteria)
{
string tempKey = criteria.Key;
string tempValue = criteria.Value;
predicate = predicate.And(p =>
p.Items.Any(s => s.Key == tempKey && s.Value == tempValue));
}
return dataContext.Products.Where(predicate.Compile());
UPDATE: Heres some sample code that I have tested which works using an IEnumerable as the source, the resulting set productsResult
contains the first and second products in products
list:
var searchCriteria = new List<Suitability>()
{
new Suitability() { Key="a", Value="b" },
new Suitability() { Key="a", Value="c" }
};
var products = new List<Product>()
{
new Product()
{
Items = new List<Suitability>() {
new Suitability() { Key="a", Value="b" },
new Suitability() { Key="a", Value="c" }}
},
new Product()
{
Items = new List<Suitability>() {
new Suitability() { Key="a", Value="b" },
new Suitability() { Key="a", Value="c" },
new Suitability() { Key="b", Value="c" }}
},
new Product()
{
Items = new List<Suitability>() {
new Suitability() { Key="c", Value="d" }}
}
};
var predicate = PredicateBuilder.True<Product>();
foreach (Suitability criteria in searchCriteria)
{
string tempKey = criteria.Key;
string tempValue = criteria.Value;
predicate = predicate.And(p => p.Items.Any(
s => s.Key == tempKey && s.Value == tempValue));
}
IEnumerable<Product> productsResult = products.Where(predicate.Compile());
Upvotes: 1
Reputation: 122624
First of all I'd like to point out that key-value pairs (AKA EAV model) are a poor choice for representing this kind of data, and this question is a perfect example of why - it's much, much harder to search for arbitrary collections of attributes than it is to search for specific properties.
Of course, it can still be done:
var suitableProducts =
from p in products
where
p.Items.Any(s => s.Key == "dental" && s.Value == "true") &&
p.Items.Any(s => s.Key == "therapies" && s.Value == "false")
select p;
It's just not as easy to write or efficient as querying on a Product
class that actually has dental
and therapies
properties as opposed to nested attributes.
If you the number of items you need to query on can change, then the easiest way to handle that is to chain together filters:
var searchItems = ...
var result = products;
foreach (var searchItem in searchItems)
{
result = result.Where(p =>
p.Items.Any(s => s.Key == searchItem.Key &&
s.Value == searchItem.Value));
}
// Do something with result
If you're looking for a more "functional" way to do it without chaining then:
var suitabilityConditions = searchItems.Select(i =>
(Predicate<Product>)(p => p.Items.Any(s =>
s.Key == searchItem.Key && s.Value == searchItem.Value)));
Predicate<Product> match = p => suitabilityConditions.All(c => c(p));
var suitableProducts = products.Where(match);
Upvotes: 2