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

3

7

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!

Tantivy answered 30/3, 2013 at 18:33 Comment(0)
B
3

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.

Boggess answered 30/3, 2013 at 20:17 Comment(2)
Thanks Gert, very helpful. I think I will install LinqKit into my project and give it a go. It is a shame adding the delegates together does not build a filter like I had hoped, but LinqKit looks like a good solution, and it looks fairly clean syntactically. My project is using EF5 and SQL2012 in case anyone was wondering.Tantivy
Lasse and Nicholas explained about adding multicast delegates together, but Gert's suggested solution best answered: "Anyone got a better suggestion?". I have implemented a solution using LinqKit as suggested. Thanks ;o)Tantivy
P
7

That's a side-effect of the fact that the Compile method returns a delegate.

Internally, a delegate is a multicast-delegate, it can involve multiple chained references to methods.

The + is in this case just a quick way to chain them together. You can read more about combining delegates in the How to: Combine Delegates on MSDN.

Here's a LINQPad program that demonstrates:

void Main()
{
    var filter = GetX() + GetY() + GetZ();
    filter().Dump();
}

public int X() { Debug.WriteLine("X()"); return 1; }
public int Y() { Debug.WriteLine("Y()"); return 2; }
public int Z() { Debug.WriteLine("Z()"); return 3; }

public Func<int> GetX() { return X; }
public Func<int> GetY() { return Y; }
public Func<int> GetZ() { return Z; }

Output:

X()
Y()
Z()
3

Note that it thus seems to just disregard the return value from the first method calls, and returns only the last, although it fully calls the prior methods as well.

Note that the above code is similar to this:

void Main()
{
    Func<int> x = X;
    Func<int> y = Y;
    Func<int> z = Z;

    var filter = x + y + z;
    filter().Dump();
}

public int X() { Debug.WriteLine("X()"); return 1; }
public int Y() { Debug.WriteLine("Y()"); return 2; }
public int Z() { Debug.WriteLine("Z()"); return 3; }

The short answer is thus that no, this does not do what you want it to do.

Penney answered 30/3, 2013 at 18:47 Comment(1)
Thank you Lasse. The evidence I had seen did indicate only the last method in the chain was used to filter, but I was not 100% sure of the internals.Tantivy
A
5

When you call Compile(), you are creating instances of type Func<Customer, bool>, which is a delegate.

Delegates overload the operator+ to return Multicast Delegates, which is what is happening here.

See MSDN: How to: Combine Delegates (Multicast Delegates)

So, no - adding them together would not build up a query string.


If this is for EF, you want to use Expression Trees, not Lambdas.

You can create and modify Expression Trees using the System.Linq.Expressions namespace:

MSDN: System.Linq.Expressions Namespace

and

MSDN: How to: Use Expression Trees to Build Dynamic Queries

Anabel answered 30/3, 2013 at 18:48 Comment(1)
Thank you Nicholas, I will follow up your links. I did try searching google for answers, but I assume I was searching with the wrong terms. Much obliged.Tantivy
B
3

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.

Boggess answered 30/3, 2013 at 20:17 Comment(2)
Thanks Gert, very helpful. I think I will install LinqKit into my project and give it a go. It is a shame adding the delegates together does not build a filter like I had hoped, but LinqKit looks like a good solution, and it looks fairly clean syntactically. My project is using EF5 and SQL2012 in case anyone was wondering.Tantivy
Lasse and Nicholas explained about adding multicast delegates together, but Gert's suggested solution best answered: "Anyone got a better suggestion?". I have implemented a solution using LinqKit as suggested. Thanks ;o)Tantivy

© 2022 - 2024 — McMap. All rights reserved.