0

Consider a class that can be used as a member of multiple other classes:

class Customer {
    public string FirstName {get;set;}
    public string LastName {get;set;}
}
// Both "Order" and "Profile" have a "Customer" property
class Order {
    public Customer Customer {get;set;}
}
class Profile {
    public Customer Customer {get;set;}
}

I want to define a method that makes a checker for an object associated with a Customer. If I want an in-memory checker, I do it like this:

static Func<T,bool> Check<T>(Func<T,Customer> conv, string first, string last) {
    return obj => conv(obj).FirstName == first && conv(obj).LastName == last;
}

I can use my checker for in-memory sequences as follows:

var matchingOrders = orders
    .Where(Check<Order>(x => x.Customer, "Foo", "Bar"))
    .ToList();
var matchingProfiles = profiles
    .Where(Check<Profile>(x => x.Customer, "Foo", "Bar"))
    .ToList();

Now I want to do the same thing with Expression<Func<T,bool>>:

static Expression<Func<T,bool>> Check<T>(Expression<Func<T,Customer>> conv, string first, string last)

Unfortunately, the same trick does not work:

return obj => conv(obj).FirstName == first && conv(obj).LastName == last;

and use it like this:

var matchingOrders = dbContext.Orders
    .Where(Check<Order>(x => x.Customer, "Foo", "Bar"))
    .ToList();
var matchingProfiles = dbContext.Profiles
    .Where(Check<Profile>(x => x.Customer, "Foo", "Bar"))
    .ToList();

This triggers an error:

CS0119: Expression denotes a variable', where amethod group' was expected

Can I compose expressions the same way that I compose delegates?

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523

1 Answers1

2

Unfortunately, C# does not currently provide a way to compose expressions from Expression<Func<...>> objects. You have to use expression trees, which is quite a bit longer:

static Expression<Func<T,bool>> CheckExpr<T>(Expression<Func<T,Customer>> conv, string first, string last) {
    var arg = Expression.Parameter(typeof(T));
    var get = Expression.Invoke(conv, arg);
    return Expression.Lambda<Func<T,bool>>(
        Expression.MakeBinary(
            ExpressionType.AndAlso
        ,   Expression.MakeBinary(
                ExpressionType.Equal
            ,   Expression.Property(get, nameof(Customer.FirstName))
            ,   Expression.Constant(first)
            )
        ,   Expression.MakeBinary(
                ExpressionType.Equal
            ,   Expression.Property(get, nameof(Customer.LastName))
            ,   Expression.Constant(last)
            )
        )
    ,   arg
    );
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Actually, you could take two predicate-like expression trees and then use tree-walking to create a &&-combined or ||-combined predicate-like copy - copy, because IIRC you can't just reuse the original ones which are already bound in that tree (Parameter expressions will block it at tree leaves).. It's even easier if parts take the same arguments.. I have seen it already implemented, someone created once a general-purpose Expression tree Visitor which, as one of the samples, had an example of combining where clauses by OR operator.. argh, I just find the right words to google it out right now:/ – quetzalcoatl May 20 '17 at 19:18
  • hah! found it! Sure, it's old, 2008ish, but such things don't rust! [Please see this article](https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/). It uses ExpressionVisitor from yet [another article](http://blogs.msdn.com/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx) to build `Compose`, `.And()` and `.Or()` extension methods - especially, please check the last example in the first article and see how see reusable and handy it turns out to be. (btw, it's 9yrs later now and that still is not in the BCL..) – quetzalcoatl May 20 '17 at 22:05
  • Hm.. excuse me for asking, but I probably used up all my spare time today :/ Maybe you could find some time and collect all the code bits from those articles together? As another answer to the same question.. It'd be kind of a shame if this utility were to eventually evaporate from the internet – quetzalcoatl May 20 '17 at 22:10
  • @quetzalcoatl A bit late, but I believe [LINQKit](https://github.com/scottksmith95/LINQKit) has everything needed and is easily added to projects. – NetMage Apr 19 '18 at 19:02