How to build up a dynamic OR query in LINQ? - c#

I'm trying to dynamically create a where clause with link.
Now, creating something like SELECT Product WHERE x = 123 AND y = 349 AND w = 948 is no problem. I'm just adding extra Where filters.
products = products.Where(product => product.Property == 123);
But now i have an unknown number of values that should be tested with an OR operator. The SQL Should be like SELECT Product WHERE x = 123 OR x = 499 OR x = 998 .. and n more OR's

Build a list of numbers to check and do something like the following:
var idsToCheck = new List<int>{123, 789, 654}; //Build from dynamic list
products = products.Where(x => idsToCheck.Contains(x.Id));
The above can be duplicated for v and w if required
var idsToCheck = new List<int>{123, 789, 654}; //Build from dynamic list
products = products.Where(x => idsToCheck.Contains(x.Id));
var propertyToCheck = new List<int>{123, 789, 654};//Build from dynamic list
products = products.Where(x => propertyToCheck.Contains(x.Property));
var numberToCheck = new List<int>{123, 789, 654};
products = products.Where(x => numberToCheck.Contains(x.Number));
If your values to check are already being passed in as enumerables then you don't need to build your xxxToCheck collections. You can simply do the .Contains against the originals

You can use contains on a list or just have the equation like so:
products.Where(product => (product.Property == 123) || (product.Property == 499) || (product.Property == 998));

If you want to compose an OR query from multiple where clauses you really need to compose the predicates in the where clauses instead. Afterall, chaining where clauses would be an AND.
So, if you have the following predicates:
Func<Product, bool> w1 = p => p.x == 123;
Func<Product, bool> w2 = p => p.y == 349;
Func<Product, bool> w3 = p => p.w == 948;
Func<Product, bool> w4 = p => p.Property == 123;
Func<Product, bool> w5 = p => p.x == 499;
Func<Product, bool> w6 = p => p.x == 998;
You can define the following extension method to OR them:
public static Func<T, bool> Or<T>(this Func<T, bool> #this, Func<T, bool> that)
{
return t => #this(t) || that(t);
}
Now you could write:
var products1 = products.Where(w1.Or(w5).Or(w6));
var products2 = products.Where(
((Func<Product, bool>)(p => p.x == 123))
.Or(p => p.y == 349)
.Or(p => p.Property == 123));
If you want to work with lists of clauses then add this extension method:
public static Func<T, bool> Or<T>(this IEnumerable<Func<T, bool>> #this)
{
return #this.Aggregate((Func<T, bool>)(t => true), (a, t) => a.Or(t));
}
Now you can write:
var predicates = new List<Func<Product, bool>>()
{
p => p.x == 123,
p => p.w == 948,
p => p.Property == 123,
p => p.x == 998,
};
var products3 = products.Where(predicates.Or());
If you're writing this against an IQueryable<T> provider then you need to work with Expression<Func<Product, bool>> and not just Func<Product, bool>.
Then you need these extension methods:
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> #this,
Expression<Func<T, bool>> that)
{
var param =
Expression
.Parameter(typeof(T), "t");
var body =
Expression
.Or(
Expression
.Invoke(#this, param),
Expression
.Invoke(that, param));
return
Expression
.Lambda<Func<T, bool>>(body, param);
}
public static Expression<Func<T, bool>> Or<T>(
this IEnumerable<Expression<Func<T, bool>>> #this)
{
return #this.Aggregate((Expression<Func<T, bool>>)(t => true), (a, t) => a.Or(t));
}
The calling code still works the same:
var predicatesX = new List<Expression<Func<Product, bool>>>()
{
p => p.x == 123,
p => p.w == 948,
p => p.Property == 123,
p => p.x == 998,
};
var products3X = products.Where(predicatesX.Or());
Is this what you were looking for?

Related

Aggregate Linq Expressions: getting an exception of variable referenced from scope

I have a list of expressions of type: Expression<Func<Person, bool>> and I want to aggregate them and then compile the aggregated result into a single Func<Person, bool>. I was able to create the aggregated expression but the part to compile the result aggregated expression throws an exception. Any help would be appreciated. Thank you.
Expression<Func<Person, bool>> expr1 = x => x.Age > 10;
Expression<Func<Person, bool>> expr2 = x => x.LastName == "some firstname";
Expression<Func<Person, bool>> expr3 = x => x.FirstName == "some lastname";
Expression<Func<Person, bool>> expr4 = x => x.Initial == 'a';
Expression<Func<Person, bool>> expr5 = x => x.DateOfBirth == DateTime.Now;
Expression<Func<Person, bool>> expr6 = x => x.Height > 10;
var exprList = new List<Expression<Func<Person, bool>>>()
{
expr1, expr2, expr3, expr4, expr5
};
var list = exprList
.Select(x => x.Body)
.Aggregate(Expression.AndAlso);
// this works, apparently?!
var aggregatedExpression = Expression.Lambda<Func<Person, bool>>(list, Expression.Parameter(typeof(Person), "x"));
// fails here! it cannot compile
var result = aggregatedExpression.Compile();
This is the exception:
Unhandled Exception: System.InvalidOperationException: variable 'x' of type 'TestAggregateExpression.Person' referenced from scope '', but it is not defined
at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)
I believe you need to visit all expressions in the list and to replace the parameter. Use this helper:
internal sealed class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _param;
private ParameterReplacer(ParameterExpression param)
{
_param = param;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == _param.Type ?
base.VisitParameter(_param) : // replace
node; // ignore
}
public static T Replace<T>(ParameterExpression param, T exp) where T : Expression
{
return (T)new ParameterReplacer(param).Visit(exp);
}
}
Usage:
var param = Expression.Parameter(typeof(Person), "x"); // I'd use 'p' by the way
exp = ParameterReplacer.Replace(param, exp);
In your case:
var list = exprList.Select(x => x.Body)
.Select(exp => ParameterReplacer.Replace(param, exp))
.Aggregate(Expression.AndAlso);

Expression in select in LINQ to SQL

If I work with LINQ to Objects, I can use Func<TIn, TOut> in Select, like this:
Enumerable.Range(1, 10).Select(x => new { A = x, B = SomeFunc });
where SomeFunc is something like this:
Func<int, long> SomeFunc = x => x * x;
But working with LINQ to Entities, Func doesn't work, I must use Expression. And this code doesn't work:
var query = Enumerable.Range(1, 10)
.AsQueryable()
.Select(x => new { A = x, B = SomeExpr });
where SomeExpr is something like this:
Expression<Func<int, long>> SomeExpr = x => x * x;
How can I use Expressions in Select in query?
You have to compile and execute the query
var query2 = Enumerable.Range(1, 10)
.AsQueryable()
.Select(x => new { A = x, B = SomeExpr.Compile().DynamicInvoke(x) });
Problem is that x => new {...} is already the expression you pass as an argument to Select(...). Your code will work if you compile and invoke SomeExpr into the select expression.
Expression<Func<int, long>> SomeExpr = x => x * x;
var query = Enumerable.Range(1, 10)
.AsQueryable()
.Select(x => new { A = x, B = SomeExpr.Compile().Invoke(x) });

using QueryOver's query separated

How can I use QueryOver's linq query separated like below ICriteria which will be used in a generic method?
ICriterion c = Restrictions.And(Restrictions.Eq("Name", "Foo"),
Restrictions.Or(Restrictions.Gt("Age", 21), Restrictions.Eq("HasCar",true)));
IQueryOver<Foo> c = session.QueryOver<Foo>().Where((k => k.Name == "Tiddles"
&& k => k.Age== 21) || k => k.Age < 21);
public static IList<T> All(ICriterion c)
{
using (var session = NHibernateHelper<T>.OpenSession())
{
var CC = session.CreateCriteria(typeof(T));
CC.Add(c);
return CC.List<T>();
}
}
The equivalent to the first line, in lambda form is
ICriterion cr = Restrictions.Where<Foo>(k => k.Name == "Foo" && (k.Age > 21 || k.HasCar));
You could pass in a Expression<Func<T, bool>> which is what one of the Where overloads accepts instead of a Criteria.
System.Linq.Expressions.Expression<Func<Foo, bool>> c = k =>
(k.Name == "Tiddles" && k.Age== 21) || k.Age < 21;
public static IList<T> All(Expression<Func<T, bool>> expression)
{
using (var session = NHibernateHelper<T>.OpenSession())
{
return session.QueryOver<T>()
.Where(expression)
.List();
}
}

Linq Dynamically append Where clauses with OR

I've been trying to build a linq query with dynamically added where clauses. I have a web page with a bunch of checkboxes that are selected to correspond to which fields you want to search:
What I have so far is the following:
//This calls a select from table to construct the query which the where clauses will be added to
IQueryable<AutoCompleteRestultDto> query = GetAutocompleteResults();
if (FirstName == true || AllFields == true)
{
Expression<Func<AutoCompleteRestultDto, bool>> firstNameFilter = c => terms.Any(t => c.FirstName.ToLower().Contains(t.ToLower()));
query = query.Where(firstNameFilter);
}
if (LastName == true || AllFields == true)
{
Expression<Func<AutoCompleteRestultDto, bool>> lastNameFilter = c => terms.Any(t => c.LastName.ToLower().Contains(t.ToLower()));
query = query.Where(lastNameFilter);
}
if (KnownAs == true || AllFields == true)
{
Expression<Func<AutoCompleteRestultDto, bool>> knownAsFilter = c => terms.Any(t => c.KnownAs.ToLower().Contains(t.ToLower()));
query = query.Where(knownAsFilter);
}
// etc.
return query
.Select(c => new ContactAutoCompleteModel
{
label = c.FirstName + " " + c.LastName
})
.Take(15)
.OrderBy(d => d.label)
.ToList();
The problem is this solution requires all the expressions that are tacked on to simultaneously be true ie: where(clause1 AND clause2 AND clause3)
Can't figure out how to change this to OR clauses ie: where(clause1 OR clause2 OR clause3)
You are chaining Enumerable.Where calls in the code you posted. That's why you get the AND effect. This below is a poach of PredicateBuilder using predicates instead of expressions.
public static class PredicateExtensions
{
public static Predicate<T> Or<T> (this Predicate<T> p1, Predicate<T> p2)
{
return obj => p1(obj) || p2(obj);
}
public static Predicate<T> And<T> (this Predicate<T> p1, Predicate<T> p2)
{
return obj => p1(obj) && p2(obj);
}
public static Predicate<T> False<T> () { return obj => false; }
public static Predicate<T> True<T> () { return obj => true; }
public static Predicate<T> OrAll<T> (IEnumerable<Predicate<T>> conditions)
{
Predicate<T> result = PredicateExtensions.False<T>();
foreach (Predicate<T> cond in conditions)
result = result.Or<T>(cond);
return result;
}
public static Predicate<T> AndAll<T> (IEnumerable<Predicate<T>> conditions)
{
Predicate<T> result = PredicateExtensions.True<T>();
foreach (Predicate<T> cond in conditions)
result = result.And<T>(cond);
return result;
}
}
You can use the above like so with an enumerable , customizing (apriori) the predicate enumerable on some condition:
Predicate<AutoCompleteRestultDto> firstNamePredicate =
c => terms.Any(t => c.FirstName.ToLower().Contains(t.ToLower()));
Predicate<AutoCompleteRestultDto> lastNamePredicate =
c => terms.Any(t => c.LastName.ToLower().Contains(t.ToLower()));
Predicate<AutoCompleteRestultDto> knownAsPredicate =
c => terms.Any(t => c.KnownAs.ToLower().Contains(t.ToLower()));
var all = new Predicate<AutoCompleteRestultDto>[] {
firstNamePredicate,
knownAsPredicate,
lastNamePredicate };
//
var items = query.Where(a => PredicateExtensions.OrAll(all)(a)).ToList();
items = query.Where(a => PredicateExtensions.AndAll(all)(a)).ToList();
or adding them iteratively like you do, step by step:
Predicate<AutoCompleteRestultDto> orResultPredicate =
PredicateExtensions.False<AutoCompleteRestultDto>();
if (FirstName == true || AllFields == true) {
orResultPredicate=orResultPredicate.Or(firstNamePredicate); }
if (LastName == true || AllFields == true) {
orResultPredicate = orResultPredicate.Or(lastNamePredicate); }
if (KnownAs == true || AllFields == true) {
orResultPredicate = orResultPredicate.Or(knownAsPredicate); }
Func<AutoCompleteRestultDto, bool> funcOr = c => orResultPredicate(c);
//
IQueryable<AutoCompleteRestultDto> query; // initialized already
var items = query.Where(funcOr).ToList();

IQueryable<T>.Where() suitable Expression in where?

I'm very low experienced with Expressions in .NET, that's why I rather ask you guys.
How should I - see comment below:
using P = Myclass;
..
System.Linq.Expressions.Expression<Func<P, bool>> myExpression = null;
..
myExpression1 = x => foo1 == true && foo2 == false;
myExpression2 = x => ... ;
..
BinaryExpression resultExpression = System.Linq.Expressions.Expression.OrElse(myExpression1, myExpression2);
..
IQueryable<P> l = l.Where(?resultExpression?); // how to transform BinaryExpression to the suitable type?
Thank you
You can't "OR" lambdas together that way. You really want to "OR" the lambda bodies together. Here's a method to do that:
public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
this IEnumerable<Expression<Func<T, bool>>> filters)
{
Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault();
if (firstFilter == null)
{
Expression<Func<T, bool>> alwaysTrue = x => true;
return alwaysTrue;
}
var body = firstFilter.Body;
var param = firstFilter.Parameters.ToArray();
foreach (var nextFilter in filters.Skip(1))
{
var nextBody = Expression.Invoke(nextFilter, param);
body = Expression.OrElse(body, nextBody);
}
Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
return result;
}
Then later:
Expression<Func<P, bool>> myFilter1 = x => foo1 == true && foo2 == false;
Expression<Func<P, bool>> myFilter2 = x => ... ;
..
List<Expression<Func<P, bool>>> filters = new List<Expression<Func<P, bool>>>();
filters.Add(myfilter1);
filters.Add(myfilter2);
..
Expression<Func<P, bool>> resultFilter = filters.OrTheseFiltersTogether();
IQueryable<P> query = query.Where(resultFilter);
you might want to take a wee look at the Predicatebuilder:
http://www.albahari.com/nutshell/predicatebuilder.aspx
the Predicatebuilder allows you to run up some very powerful expressions (AND/OR/NOT etc, etc) in a very clean and easy to understand way. For simple expressions, I do of course just roll them from scratch and apply but for the complex stuff...
I'm quite a fan of it :)
a few links on SO itself that may be helpful:
LINQ to SQL PredicateBuilder
Generated SQL with PredicateBuilder, LINQPad and operator ANY
.Where method takes a lambda expression as a parameter, so you need to build your BinaryExpression to a complete LambdaExpression.
var resultExpression = Expression.OrElse(myExp1, myExp2);
// now the exp is like: p > 100 || p < 10
ParameterExpression parameterExp = Expression.Parameter(typeof(P),"p");
// construct a parameter with its type P, looks like: p =>
LambdaExpression lambdaExp = Expression.Lambda(resultExpression, parameterExp);
// merge the two expressions: p => p > 100 || p < 10
myList.Where(lambdaExp.Compile());
Its a combination of two Func<P, bool> on expression level.
A less fancy way to do the same should be:
Func<P, bool> myExpression1 = x => foo1 == true && foo2 == false;
Func<P, bool> myExpression2 = x => ... ;
IQueryable<P> l = l.Where((p) => myExpression1(p) || myExpression2(p));

Categories