1

Is it possible to create a "meta-predicate" by writing a a function that takes 2 (or 4 if necessary) lambdas which represent the left and right side properties (operands) and have it generate an predicate. Something like the following code sample:

public Expression<Func<Something,bool>> StringEquals<Something>(Expression<Something> leftOperand, Expression<Something> leftOperandSelector){
    return (rightOperandSelector, leftOperandSelector) => (
        (rightOperandSelector == leftOperandSelector) 
        || (
            string.IsNullOrEmpty(rightOperandSelector) && 
            string.IsNullOrEmpty(leftOperandSelector)
        )
    );
}

OR:

public Expression<Func<Something,bool>> DatesActive<Something>(Expression<Something> startDateOperandSelector, Expression<Something> endDateOperandSelector){
    return (startDateOperandSelector, endDateOperandSelector) => (
    (startDatePropertySelector >= DateTime.Now) 
    && (endDatePropertySelector <= DateTime.Now)
    );
}

Where SomeStringProperty for each side or startDatePropertySelector or endDatePropertySelector is defined by a lambda? I have not figured out how to dynamically pass the Operand of the predicate expression.

Ideally I would like to be able to inline it like this:

return new Expression<Func<Request,bool>>[]{
    r => (r.Id != request.Id) && (!r.Reviewed),
    StringEquals(r => r.VendorName, request=>request.VendorName),
    NotExpired(r => r.ContractStart, request=>request.ContractEnd),                        
    ...
};

*Does someone have an idea on the best way to approach this? My interest is in creating "meta"-expressions for easy use where I am using the same expression repeatedly over multiple properties. Concrete example is given just for reference/explanation. Very open to knowing if this is silly and if there is a better approach, happy to learn. *

More background below if desired.

Background: After a simpler form of this Expression was in place I was forced to refactor to handle the fact that EntityFramework doesn't treat equality how you would expect in that instead of interpreting C# like the following: (rightSide.SomeStringProperty == leftSide.SomeStringProperty) as a two part SQL expression like this

(
    (rightSide.SomeStringProperty IS NULL AND leftSide.SomeStringProperty IS NULL)
    OR (rightSide.SomeStringProperty = leftSide.SomeStringProperty)
)

it more literally tranlates it as:

(rightSide.SomeStringProperty = leftSide.SomeStringProperty)

which of course will not return values for where both sides are null. Apparently this has been corrected in EF6 (Correction: @Slauma points out this is available in EF5 via the UseCSharpNullComparisonBehavior. I am using EF4 and cannot upgrade for this release.)

I want to avoid more repetitive code like the following:

            var where = new Expression<Func<Request,bool>>[]{
                    r => (r.Id != request.Id) && (!r.Reviewed) 
                    && (
                        (r.Address == request.Address) 
                        || (string.IsNullOrEmpty(r.Address) && string.IsNullOrEmpty(request.Address))
                    )
                    && (
                        (r.City == request.City) 
                        || (string.IsNullOrEmpty(r.City) && string.IsNullOrEmpty(request.City))
                    )
                    && (
                        (r.Province == request.Province) 
                        || (string.IsNullOrEmpty(r.Province) && string.IsNullOrEmpty(request.Province))
                    )
                    && (
                        (r.PostalCode == request.PostalCode) 
                        || (string.IsNullOrEmpty(r.PostalCode) && string.IsNullOrEmpty(request.PostalCode))
                    )
                    && (
                        (r.Website == request.Website) 
                        || (string.IsNullOrEmpty(r.Website) && string.IsNullOrEmpty(request.Website))
                    )
                };
j0tt
  • 1,108
  • 1
  • 7
  • 16
  • 2
    Can you just use the ?? operator? `(r.City ?? "") == (request.City ?? "")` – Jay Jun 24 '13 at 18:45
  • That did let me refactor into something much more concise. Thanks! However if I wasn't treating empty string and null the same this would not work here. I was more interested in the ability to parameterize something like a meta-expression that would be repeated for multiple fields. – j0tt Jun 24 '13 at 18:57
  • Things like that get tricky. EF has to translate the code you write into an actual SQL statement (`?? == ISNUL()`), if it can't it will blow up. Usually in situations like this i build query chains and check the conditionals outside of query itself. – Jay Jun 24 '13 at 19:24
  • 2
    BTW: The correct `null` comparison exists since EF 5 via a context configuration flag that can be enabled (`UseCSharpNullComparisonBehavior`): http://stackoverflow.com/a/15503871/270591 – Slauma Jun 24 '13 at 21:23
  • @Slauma Thanks for that! I am using EF4 at the moment and can't upgrade in this release, but that is helpful for the future. (I should have stated in the question.) While I do have a concrete example I am most interested in "Is it possible to pass a property expression as a lambda to a method for use in another lambda which will operate on that property expression?" Do you think I should I change the title of my question? – j0tt Jun 25 '13 at 14:13

2 Answers2

2

You can use the System.Linq.Expressions namespace to build an expression by hand. For the two examples you've posted, the following should work:

public static Expression<Func<T, bool>> StringEquals<T>(Expression<Func<T, string>> leftOperand, Expression<Func<T, string>> rightOperand)
{
   var p = leftOperand.Parameters[0];
   var leftOperandBody = leftOperand.Body;
   var rightOperandBody = ReplacementVisitor.Transform(rightOperand, rightOperand.Parameters[0], p);

   var isNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty");
   var leftNullOrEmpty = Expression.Call(isNullOrEmptyMethod, leftOperandBody);
   var rightNullOrEmpty = Expression.Call(isNullOrEmptyMethod, rightOperandBody);
   var bothNullOrEmpty = Expression.AndAlso(leftNullOrEmpty, rightNullOrEmpty);
   var areEqual = Expression.Equal(leftOperandBody, rightOperandBody);
   var body = Expression.OrElse(bothNullOrEmpty, areEqual);

   return Expression.Lambda<Func<T, bool>>(body, p);
}

public static Expression<Func<T, bool>> DatesActive<T>(Expression<Func<T, DateTime>> startDate, Expression<Func<T, DateTime>> endDate)
{
   var p = startDate.Parameters[0];
   var startDateBody = startDate.Body;
   var endDateBody = ReplacementVisitor.Transform(endDate, endDate.Parameters[0], p);

   var nowProperty = typeof(DateTime).GetProperty("Now");
   var nowValue = Expression.Property(null, nowProperty);
   var startValid = Expression.GreaterThanOrEqual(startDateBody, nowValue);
   var endValid = Expression.LessThanOrEqual(endDateBody, nowValue);
   var body = Expression.AndAlso(startValid, endValid);

   return Expression.Lambda<Func<T, bool>>(body, p);
}

internal sealed class ReplacementVisitor : ExpressionVisitor
{
   private IList<ParameterExpression> SourceParameters { get; set; }
   private Expression Find { get; set; }
   private Expression Replace { get; set; }

   public static Expression Transform(LambdaExpression source, Expression find, Expression replace)
   {
      var visitor = new ReplacementVisitor
      {
         SourceParameters = source.Parameters,
         Find = find,
         Replace = replace,
      };

      return visitor.Visit(source.Body);
   }

   private Expression ReplaceNode(Expression node)
   {
      return (node == Find) ? Replace : node;
   }

   protected override Expression VisitConstant(ConstantExpression node)
   {
      return ReplaceNode(node);
   }

   protected override Expression VisitBinary(BinaryExpression node)
   {
      var result = ReplaceNode(node);
      if (result == node) result = base.VisitBinary(node);
      return result;
   }

   protected override Expression VisitParameter(ParameterExpression node)
   {
      if (SourceParameters.Contains(node)) return ReplaceNode(node);
      return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
   }
}
Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
  • Thank you! I appreciate you taking the time to show me how to do this, I had not seen a lot of what you are doing here. It is very close to what I am looking for though I was really hoping that I could have the body of these methods be closer to a straight lambda expression so they would be easier to read and maintain. I will test it out and let you know how it goes. – j0tt Jun 26 '13 at 12:47
0

This can be done by dynamically composing expression trees, as Richard demonstrated, though it needn't be quite so complex. Specifically, the visitor pattern adds unnecessary overhead. Consider this simpler solution to your first example:

using E = System.Linq.Expressions.Expression;

/* ... */

private static readonly MethodInfo IsNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty");

public static Expression<Func<bool>> StringEquals(Expression<Func<String>> leftAccessor, Expression<Func<String>> rightAccessor)
{
    var left = E.Parameter(typeof(string), "left");
    var right = E.Parameter(typeof(string), "left");

    // () => {
    //     string left = leftAccessor();
    //     string right = rightAccessor();
    //     
    //     return left == right ||
    //            string.IsNullOrEmpty(left) && string.IsNullOrEmpty(right);
    // }

    return E.Lambda<Func<bool>>(
        E.Block(
            new[] { left, right },
            E.Assign(left, E.Invoke(leftAccessor)),
            E.Assign(right, E.Invoke(rightAccessor)),
            E.OrElse(
                E.Equal(left, right),
                E.AndAlso(
                    E.Call(IsNullOrEmptyMethod, left),
                    E.Call(IsNullOrEmptyMethod, right)))));
}

You can apply a similar technique to devise a solution to your second example. Note the lack of any generic parameters: the actual item containing the properties need not be exposed. You could use this method to compare two properties on the same object; the same property on two different objects; or any arbitrary values.

Note that the lambda compiler will take care of inlining simple accessors like () => r.Address. It can do this easily since the accessors are expressions themselves.

EDIT: Reading your question again, I see you are using Entity Framework. I am not sure if EF's query provider is sophisticated enough to inline the accessors. If it isn't, this may not work, in which case it may be necessary to do some manual transformation as Richard did in his answer. I would be interested in hearing whether this works for your case or not. I'll leave this answer either way, as it may be useful to someone who isn't using EF.

Also, as @svick points out in the comments, EF almost certainly does not support block expressions. You would probably have to construct the lambda as follows:

return E.Lambda<Func<bool>>(
    E.OrElse(
        E.Equal(E.Invoke(leftAccessor), E.Invoke(rightAccessor)),
        E.AndAlso(
            E.Call(IsNullOrEmptyMethod, E.Invoke(leftAccessor)),
            E.Call(IsNullOrEmptyMethod, E.Invoke(rightAccessor)))));
Mike Strobel
  • 25,075
  • 57
  • 69