Friday, November 28, 2008

Using MetaLinq to simulate NHibernate’s DetachedCriteria in Entity Framework

NHibernate’s DetachedCriteria allow you to specify a query filter outside session context and then pass it to code that actually uses a session in order to retrieve data. What’s more, DetachedCriteria is serializable, so you could imagine a scenario in which a client forms criteria and passes it to a service in order to query data.

Entity Framework has no such thing as DetachedCriteria, but you can use LINQ and expression trees in similar fashion. The only problem is that the expression trees are immutable and nonserializable. There is however a project called MetaLinq that copies an expression tree’s structure into mutable clone (built from custom set of classes that mirror the System.Linq.Expressions namespace) and enables you to both edit the tree and serialize it.

So I performed a little experiment. Following is the code of WCF service operation.

public Customer[] GetCustomersBy(EditableLambdaExpression filter)
    var filterLambda = filter.ToExpression() as LambdaExpression;
    var filterDelegate = filterLambda.Compile() as Func<Customer, bool>;

    using (var context = new NorthwindContext())
        return context.CustomerSet.Where(filterDelegate).ToArray();

The method receives a filter in a parameter. It is of type EditableLambdaExpression, which is one of MetaLinq’s expression types. By calling it’s ToExpression method you get the regular expression tree. Then there’s a compilation resulting in a delagate of type Func<Customer,bool> (it’s a quiet assumption) that is finally passed to the Where method of ObjectContext. Quite straightforward.

So is the client code:

using (Service1Client client = new Service1Client())
    var filter = EditableExpression.CreateEditableExpression(
        (Customer c) => c.CustomerID == "ALFKI") as EditableLambdaExpression;

    var customers = client.GetCustomersBy(filter);

The filter expressed as a lambda has to be converted to MetaLinq’s EditableLambdaExpression. Then it can be serialized and passed to the service. And this works just fine. What also works is:

var filter = EditableExpression.CreateEditableExpression(
    (Customer c) => c.CustomerID.StartsWith("A")) as EditableLambdaExpression;

What does not work is for example:

var filter = EditableExpression.CreateEditableExpression(
    (Customer c) => c.Orders.Any(o => o.Freight > 100.0M)) 
    as EditableLambdaExpression;

It turns out MetaLinq (as for now) is having trouble deserializing expressions with generic and extension methods.

This was an interesting experiment and now there is time for:

The criticism

I wouldn’t recommend using this idea in any production environment. It has several disadvantages:

  • MetaLinq itself is just an experiment, it definitely is not finished. There potentially are some hidden problems and you can’t express more advanced scenarios (unless of course you fix the code yourself :).
  • Building mutable expressions, serializing them, then deserializing and then compiling again imposes a performance penalty.
  • You can’t enforce the lambda passed to your service to be Func<Customer, bool>. You always has to check it and throw when it’s not.
  • And last but not least – it threats security of your service. What you’re doing is just executing some code that came from outside the service’s boundaries. This could allow someone to perform, let’s say, LINQ injection :)


DotNetKicks Image

No comments: