Search code examples
c#linqlambdacompiled

What's the purpose of adding compiled Func methods together?


I have seen that is is possible to add compiled methods together.

Expression<Func<Customer, bool>> ln = c => c.lastname.Equals(_customer.lastName, StringComparison.InvariantCultureIgnoreCase);
Expression<Func<Customer, bool>> fn = c => c.firstname.Equals(_customer.firstName, StringComparison.InvariantCultureIgnoreCase);
Expression<Func<Customer, bool>> fdob = c => c.DOB.ToString("yyyyMMdd").Equals(_customer.DOB.ToString("yyyyMMdd"));

var filter = ln.Compile() + fn.Compile() + fdob.Compile();

Does it make sense to do this?

I would intend to use the filter in place of a lambda expression to filter a repository of customers:

IEnumerable<Customer> customersFound = _repo.Customers.Where(filter);

Depending on business logic, I may or may not add the three compiled methods together, but pick and choose, and possibly add more compiled methods as I go.

Can anyone explain if adding them together would build up a query string or not? Anyone got a better suggestion?

I could chain "Where" statements and use regular lambda expressions, but I am intrigued by what you might gain from compiling methods and adding them up!


Solution

  • It is a creative effort to chain predicates and too bad it does not work. The reason why has been explained excellently by Lasse and Nicholas (+2). But you also ask:

    Anyone got a better suggestion?

    The drawback of compiled expressions is that they're not expressions any more and thus, can't be used with IQueryables that are backed by SQL query providers (like linq to sql or linq to entities). I assume _repo.Customers is such an IQueryable.

    If you want to chain expressions dynamically, LINQKit's PredicateBuilder is an excellent tool to do this.

    var pred = Predicate.True<Customer>();
    
    pred = pred.And(c => c.lastname.Equals(_customer.lastName, StringComparison.InvariantCultureIgnoreCase);
    pred = pred.And(c => c.firstname.Equals(_customer.firstName, StringComparison.InvariantCultureIgnoreCase);
    pred = pred.And(c => c.DOB.ToString("yyyyMMdd").Equals(_customer.DOB.ToString("yyyyMMdd"));
    
    var customersFound = _repo.Customers.Where(pred.Expand());
    

    This is what the Expand is for:

    Entity Framework's query processing pipeline cannot handle invocation expressions, which is why you need to call AsExpandable on the first object in the query. By calling AsExpandable, you activate LINQKit's expression visitor class which substitutes invocation expressions with simpler constructs that Entity Framework can understand.

    Or: without it an expression is Invoked, which causes an exception in EF:

    The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

    BTW. If you use linq to sql/entities you may as well use c.lastname == _customer.lastName because it is translated into SQL and the database collation will determine case sensitiveness.