griegs
griegs

Reputation: 22760

Lambda and Nested Objects

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

Answers (2)

Simon Fox
Simon Fox

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

Aaronaught
Aaronaught

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

Related Questions