Intro
In the application I 'm currently working on, there are two kinds of each business object: the "ActiveRecord" kind and the "DataContract" kind. So for example, there would be:
namespace ActiveRecord {
class Widget {
public int Id { get; set; }
}
}
namespace DataContract {
class Widget {
public int Id { get; set; }
}
}
The database access layer takes care of translating between families: you can tell it to update a DataContract.Widget
and it will magically create an ActiveRecord.Widget
with the same property values and save that instead.
The problem 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 IDbAccessLayer {
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 am passing in an expression tree instead of a lambda because inside IDbAccessLayer
I am querying an IQueryable<ActiveRecord.Widget>
; to do that efficiently (think LINQ to SQL) I need to pass in an expression tree so this method asks for just that.
The snag: the parameter needs to be magically transformed from an Expression<Func<DataContract.Widget, bool>>
to an Expression<Func<ActiveRecord.Widget, bool>>
.
Attempted Solution
What I '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 property of type MemberInfo
that describes DataContract.Widget.Id
.
There are also ParameterExpression
instances both in the expression tree and in its parameter collection (predicate.Parameters
) that describe DataContract.Widget
; all of this will result in errors since the queryable body does not contain that type of widget but rather ActiveRecord.Widget
.
After searching a bit, I found System.Linq.Expressions.ExpressionVisitor
(its source can be found here in the context of a how-to), which offers a convenient way to modify an expression tree. In .NET 4, this class is included out of the box.
Armed with this, I implemented a visitor. This simple visitor only takes care of changing the types in member access and parameter expressions, but that's enough functionality to work with the predicate w => w.Id == 0
.
internal class Visitor : ExpressionVisitor
{
private readonly Func<Type, Type> typeConverter;
public Visitor(Func<Type, Type> typeConverter)
{
this.typeConverter = typeConverter;
}
protected override Expression VisitMember(MemberExpression node)
{
var dataContractType = node.Member.ReflectedType;
var activeRecordType = this.typeConverter(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.typeConverter(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, and 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 the 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 the predicate is w => true
, everything works fine. Therefore I am assuming that I 'm not doing enough work in the visitor, but I can't find any more leads to follow.
Final Solution
After taking into account the correct answers to the problem (two of them below; one short, one with code) the problem was solved; I put the code along with a few important notes in a separate answer to keep this long question from becoming even longer.
Thanks to everyone for your answers and comments!
Question&Answers:
os