Entity Framework and many WHERE clauses

So today, I needed to get Entity Framework to return me a list of Products from the database based on a list of Product.Name and Product.Version values (not Ids). If it were Product.Id, it would have been simple as I could have used an IN statement, but it wasn’t.

The query might get many (maybe hundreds at a time) products based on the list. Here is the query I imagined.

So when doing a query like this, since there could be hundreds, I have a couple of options.

  1. Query the database once for each product.

    [sql]
    SELECT * FROM dbo.Product
    WHERE (Name = ‘Product 1’ AND Version = ‘10.0’)
    [/sql]

    Repeat this same query once for each Product.

  2. Query the database one time with an or clause for each Product.Name and Product.Version.

    [sql]
    SELECT * FROM dbo.Product
    WHERE (Name = ‘Product 1’ AND Version = ‘10.0’)
    OR (Name = ‘Product 2’ AND Version = ‘10.0’)
    OR (Name = ‘Product 3’ AND Version = ‘10.0’)
    OR (Name = ‘Product 4’ AND Version = ‘1.0’)
    — There could be hundreds
    [/sql]

  3. Query the database once and get all products and use code to find the ones I wanted.

    [sql]
    SELECT * FROM dbo.Product
    [/sql]

Option 1
I didn’t like this option because I could end up doing hundreds of single queries. That doesn’t sound like a good idea. What would the performance impact would be when doing hundreds of single queries? The overhead of traversing over the network to the database would prevent this option from scaling.

Option 2
This is the option I imagined in my head. My gut said to use this option.

Option 3
This would work. We only have about two thousand products today and querying them all would, right now, not be bad at all. However, we just bought a company and will be adding more products. We plan to buy more companies. Also, we have two companies that we have already bought and have yet to add those products in. When would the number of Product rows in the database make the SELECT * and invalid option? Doing this would work now, but it leave a time bomb for some future developer encounter and have to fix.

Winner: Option 2

Problem
Entity Framework doesn’t really have an easy way to create the Option 2 query.

So, how do I create this query with many where statements?

Here are the two options I’ve found:

Inline SQL Query with Entity Framework

[csharp title=”inline query”]
private static List<Product> GetProductsByNameAndVersion(ActivationDbContext dbContext, IEnumerable<ProductRequest> products)
{
if (!products.Any())
{
return new List<Product>();
}
var query = "Select * FROM Product WHERE ";
var or = "";
var template = "(Name = ‘{0}’ AND Version = ‘{1}’)";
foreach (var prod in products)
{
query += or;
query += string.Format(template, prod.Name, prod.Version);
or = " OR ";
}
var dbProducts = dbContext.Products.SqlQuery(query).ToList();
return dbProducts.ToList();
}
[/csharp]

This option means I have to create magic strings and make sure that I handle the strings correctly. It has bugs already. Such as what if a product only has a name and not a version (version could be null or empty, who knows) or vice-versa? How would this affect my query string?

PredicateBuilder

Predicate Builder from the LinqKit library which is available as a NuGet package.

[csharp]
private static List<Product> GetProductsByNameAndVersionPredicate(ActivationDbContext dbContext, IEnumerable<ProductRequest> products)
{
if (!products.Any())
{
return new List<Product>();
}

var predicate = PredicateBuilder.False<Product>();

foreach (var prod in products)
{
var inner = PredicateBuilder.True<Product>();
inner = inner.And(p => p.Name== prod.Name);
inner = inner.And(p => p.Version == prod.Version);
predicate = predicate.Or(inner);
}
var dbProducts = dbContext.Products.AsExpandable().Where(predicate).ToList();
return dbProducts;
}
[/csharp]

PredicateBuilder isn’t very intuitive. For starters, what is the different between these methods:

  • PredicateBuilder.True() – from what I understand this would be more appropriate and understandable as PredicateBuilder.And()
  • PredicateBuilder.False() – from what I understand this would be more appropriate and understandable as PredicateBuilder.Or()

Also, you have to remember to call AsExpandable() on the first call to a table in order to use it.

Conclusion

I am going to go with PredicateBuilder for now. It feels cleaner than rolling my own string query. But both solutions ultimately worked. That means that Entity Framework ultimately provided me a solution without an extra library. However, LinqKit saved me from magic strings. My only question is this: Why isn’t a predicate builder built into Entity Framework?

Leave a Reply

How to post code in comments?

*