Introduction
In the application I'm working on now, there are two types of each business object: type "ActiveRecord" and view "DataContract". So, for example, it would be:
namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } }
The database access level provides translation between families: you can tell him about the DataContract.Widget update, and he will magically create ActiveRecord.Widget with the same property values ββand save them.
The problem occurred while trying to reorganize this level of access to the database.
Problem
I want to add the following methods to the database access level:
// Widget is DataContract.Widget interface IDbAccessLayer { IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate); }
The above is a simple general purpose get method with a custom predicate. The only thing interesting is that I pass an expression tree instead of a lambda, because inside IDbAccessLayer I request IQueryable<ActiveRecord.Widget> ; to do this efficiently (think about LINQ to SQL) I need to pass an expression tree so this method asks for exactly that.
Trap: the parameter must be magically converted from Expression<Func<DataContract.Widget, bool>> to Expression<Func<ActiveRecord.Widget, bool>> .
Attempt to solve
What I would like to do inside GetMany :
IEnumerable<DataContract.Widget> GetMany( Expression<Func<DataContract.Widget, bool>> predicate) { var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>( predicate.Body, predicate.Parameters);
This will not work, because in a typical scenario, for example, if:
predicate == w => w.Id == 0;
... the expression tree contains an instance of MemberAccessExpression , which has a property of type MemberInfo that describes DataContract.Widget.Id . There are also instances of ParameterExpression both in the expression tree and in its set of parameters ( predicate.Parameters ) that describe DataContract.Widget ; all this will lead to errors, since the request body does not contain this kind of widget, but rather ActiveRecord.Widget .
After some searching, I found System.Linq.Expressions.ExpressionVisitor (its source can be found here in the context of practical use), which offers a convenient way to modify the expression tree. In .NET 4, this class is included out of the box.
Armed with this, I introduced the visitor. This simple visitor only cares about changing types in member access and parameter expressions, but this is enough 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);
results
The good news is that lambda built just fine. The bad news is that it does not work; it exploded me when I try to use it, and exception messages really don't help at all.
I studied lambda, which produces my code, and hard-coded 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 w => w.Id == 0 , lambda looks exactly like referenceLambda . But the latter works, for example, IQueryable<T>.Where , and the first does not; I tried this in the nearest debugger window.
I should also mention that when the predicate w => true , everything works fine. Therefore, I suppose that I am not doing enough work with the visitor, but I can no longer find to follow.
Final decision
After accepting the correct answers to the problem (two of them below, one short, one with code), the problem was solved; I added the code along with a few important notes in a separate answer so that this long question does not get any longer.
Thank you all for your answers and comments!