Mutating the expression tree of a predicate to target another type
Posted
by Jon
on Stack Overflow
See other posts from Stack Overflow
or by Jon
Published on 2010-05-09T09:54:45Z
Indexed on
2010/05/09
9:58 UTC
Read the original article
Hit count: 370
Intro
In the application I 'm currently working on, there are two kinds of each business object: the "ActiveRecord" type, and the "DataContract" type. So for example, we have:
namespace ActiveRecord {
class Widget {
public int Id { get; set; }
}
}
namespace DataContracts {
class Widget {
public int Id { get; set; }
}
}
The database access layer takes care of "translating" between hierarchies: you can tell it to update a DataContracts.Widget
, and it will magically create an ActiveRecord.Widget
with the same property values and save that.
The problem I have surfaced when attempting to refactor this database access layer.
The Problem
I want to add methods like the following to the database access layer:
// Widget is DataContract.Widget
interface DbAccessLayer {
IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate);
}
The above is a simple general-use "get" method with custom predicate. The only point of interest is that I 'm not passing in an anonymous function but rather an expression tree. This is done because inside DbAccessLayer we have to query ActiveRecord.Widget efficiently (LINQ to SQL) and not have the database return all ActiveRecord.Widget instances and then filter the enumerable collection. We need to pass in an expression tree, so we ask for one as the parameter for GetMany
.
The snag: the parameter we have needs to be magically transformed from an Expression<Func<DataContract.Widget, bool>>
to an Expression<Func<ActiveRecord.Widget, bool>>
.
This is where I haven't managed to pull it off...
Attempted Solution
What we 'd like to do inside GetMany
is:
IEnumerable<DataContract.Widget> GetMany(
Expression<Func<DataContract.Widget, bool>> predicate)
{
var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
predicate.Body,
predicate.Parameters);
// use lambda to query ActiveRecord.Widget and return some value
}
This won't work because in a typical scenario, for example if:
predicate == w => w.Id == 0;
...the expression tree contains a MemberAccessExpression
instance which has a MemberInfo
property (named Member) that point to members of DataContract.Widget
.
There are also ParameterExpression
instances both in the expression tree and in its parameter expression collection (predicate.Parameters
);
After searching a bit, I found System.Linq.Expressions.ExpressionVisitor (its source can be found here in the context of a how-to, very helpful) which is a convenient way to modify an expression tree.
Armed with this, I implemented a visitor. This simple visitor only takes care of changing the types in member access and parameter expressions. It may not be complete, but it's fine for the expression w => w.Id == 0
.
internal class Visitor : ExpressionVisitor
{
private readonly Func<Type, Type> dataContractToActiveRecordTypeConverter;
public Visitor(Func<Type, Type> dataContractToActiveRecordTypeConverter)
{
this.dataContractToActiveRecordTypeConverter = dataContractToActiveRecordTypeConverter;
}
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = this.dataContractToActiveRecordTypeConverter(dataContractType);
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
activeRecordType.GetProperty(node.Member.Name));
return converted;
}
protected override Expression VisitParameter(ParameterExpression node)
{
var dataContractType = node.Type;
var activeRecordType = this.dataContractToActiveRecordTypeConverter(dataContractType);
return Expression.Parameter(activeRecordType, node.Name);
}
}
With this visitor, GetMany
becomes:
IEnumerable<DataContract.Widget> GetMany(
Expression<Func<DataContract.Widget, bool>> predicate)
{
var visitor = new Visitor(...);
var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
visitor.Visit(predicate.Body),
predicate.Parameters.Select(p => visitor.Visit(p));
var widgets = ActiveRecord.Widget.Repository().Where(lambda);
// This is just for reference, see below
Expression<Func<ActiveRecord.Widget, bool>> referenceLambda =
w => w.Id == 0;
// Here we 'd convert the widgets to instances of DataContract.Widget and
// return them -- this has nothing to do with the question though.
}
Results
The good news is that lambda
is constructed just fine. The bad news is that it isn't working; it's blowing up on me when I try to use it (the exception messages are really not helpful at all).
I have examined the lambda my code produces and a hardcoded lambda with the same expression; they look exactly the same. I spent hours in the debugger trying to find some difference, but I can't.
When predicate
is w => w.Id == 0
, lambda
looks exactly like referenceLambda
. But the latter works with e.g. IQueryable<T>.Where
, while the former does not (I have tried this in the immediate window of the debugger).
I should also mention that when predicate
is w => true
, it all works just fine. Therefore I am assuming that I 'm not doing enough work in Visitor
, but I can't find any more leads to follow on.
Can someone point me in the right direction? Thanks in advance for your help!
© Stack Overflow or respective owner