Servy's answer is great and has been very useful for me. I've taken it and expanded/changed it slightly, and will add it onto this to pay back a little bit.
First off, I renamed the True and False properties to be BaseAnd (instead of True) and BaseOr (instead of false). Mainly this was to be more understandable to me based on how I use them in order to get the results I desire.
In addition, I added two new generic functions: AddToPredicateTypeBasedOnIfAndOrOr, which takes two ref predicates, one for ands and one for ors and will add an expression onto one of them depending on if it's supposed to be an and or not. This is just to reduce code duplication as my code doesn't know which type it's supposed to be before the app runs.
CombineOrPreicatesWithAndPredicates takes an initial predicate expression, an and predicate expression and an or predicate expression and combine them in a sql logical way, (and list) and (or list). This is also to reduce code duplication.
Hope this helps someone out there.
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> BaseAnd<T>() { return f => true; }
public static Expression<Func<T, bool>> BaseOr<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
public static Expression<Func<T, bool>> CombineOrPreicatesWithAndPredicates<T>(this Expression<Func<T, bool>> combinedPredicate,
Expression<Func<T, bool>> andPredicate, Expression<Func<T, bool>> orPredicate)
{
combinedPredicate = combinedPredicate ?? BaseAnd<T>();
if (andPredicate != null && orPredicate!=null)
{
andPredicate = andPredicate.And(orPredicate);
combinedPredicate = combinedPredicate.And(andPredicate);
}
else if (orPredicate!=null)
{
combinedPredicate = combinedPredicate.And(orPredicate);
}
else
{
combinedPredicate = combinedPredicate.And(andPredicate);
}
return combinedPredicate;
}
public static void AddToPredicateTypeBasedOnIfAndOrOr<T>(ref Expression<Func<T, bool>> andPredicate,
ref Expression<Func<T, bool>> orPredicate, Expression<Func<T, bool>> newExpression, bool isAnd)
{
if (isAnd)
{
andPredicate = andPredicate ?? BaseAnd<T>();
andPredicate = andPredicate.And(newExpression);
}
else
{
orPredicate = orPredicate ?? BaseOr<T>();
orPredicate = orPredicate.Or(newExpression);
}
}
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}