Note that there are some explanatory texts on larger screens.

plurals
  1. POMutating the expression tree of a predicate to target another type
    text
    copied!<h2>Intro</h2> <p>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:</p> <pre><code>namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } } </code></pre> <p>The database access layer takes care of translating between families: you can tell it to update a <code>DataContract.Widget</code> and it will magically create an <code>ActiveRecord.Widget</code> with the same property values and save that instead.</p> <p>The problem surfaced when attempting to refactor this database access layer.</p> <h2>The Problem</h2> <p>I want to add methods like the following to the database access layer:</p> <pre><code>// Widget is DataContract.Widget interface IDbAccessLayer { IEnumerable&lt;Widget&gt; GetMany(Expression&lt;Func&lt;Widget, bool&gt;&gt; predicate); } </code></pre> <p>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 <code>IDbAccessLayer</code> I am querying an <code>IQueryable&lt;ActiveRecord.Widget&gt;</code>; to do that efficiently (think LINQ to SQL) I need to pass in an expression tree so this method asks for just that.</p> <p>The snag: the parameter needs to be magically transformed from an <code>Expression&lt;Func&lt;DataContract.Widget, bool&gt;&gt;</code> to an <code>Expression&lt;Func&lt;ActiveRecord.Widget, bool&gt;&gt;</code>.</p> <h2>Attempted Solution</h2> <p>What I 'd like to do inside <code>GetMany</code> is:</p> <pre><code>IEnumerable&lt;DataContract.Widget&gt; GetMany( Expression&lt;Func&lt;DataContract.Widget, bool&gt;&gt; predicate) { var lambda = Expression.Lambda&lt;Func&lt;ActiveRecord.Widget, bool&gt;&gt;( predicate.Body, predicate.Parameters); // use lambda to query ActiveRecord.Widget and return some value } </code></pre> <p>This won't work because in a typical scenario, for example if:</p> <pre><code>predicate == w =&gt; w.Id == 0; </code></pre> <p>...the expression tree contains a <code>MemberAccessExpression</code> instance which has a property of type <code>MemberInfo</code> that describes <code>DataContract.Widget.Id</code>. There are also <code>ParameterExpression</code> instances both in the expression tree and in its parameter collection (<code>predicate.Parameters</code>) that describe <code>DataContract.Widget</code>; all of this will result in errors since the queryable body does not contain that type of widget but rather <code>ActiveRecord.Widget</code>.</p> <p>After searching a bit, I found <a href="http://msdn.microsoft.com/en-us/library/system.linq.expressions.expressionvisitor%28v=VS.100%29.aspx" rel="nofollow noreferrer"><code>System.Linq.Expressions.ExpressionVisitor</code></a> (its source can be found <a href="http://msdn.microsoft.com/en-us/library/bb882521%28VS.90%29.aspx" rel="nofollow noreferrer">here</a> 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.</p> <p>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 <code>w =&gt; w.Id == 0</code>.</p> <pre><code>internal class Visitor : ExpressionVisitor { private readonly Func&lt;Type, Type&gt; typeConverter; public Visitor(Func&lt;Type, Type&gt; 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); } } </code></pre> <p>With this visitor, <code>GetMany</code> becomes:</p> <pre><code>IEnumerable&lt;DataContract.Widget&gt; GetMany( Expression&lt;Func&lt;DataContract.Widget, bool&gt;&gt; predicate) { var visitor = new Visitor(...); var lambda = Expression.Lambda&lt;Func&lt;ActiveRecord.Widget, bool&gt;&gt;( visitor.Visit(predicate.Body), predicate.Parameters.Select(p =&gt; visitor.Visit(p)); var widgets = ActiveRecord.Widget.Repository().Where(lambda); // This is just for reference, see below Expression&lt;Func&lt;ActiveRecord.Widget, bool&gt;&gt; referenceLambda = w =&gt; 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. } </code></pre> <h2>Results</h2> <p>The good news is that <code>lambda</code> 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.</p> <p>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.</p> <p>When the predicate is <code>w =&gt; w.Id == 0</code>, <code>lambda</code> looks exactly like <code>referenceLambda</code>. But the latter works with e.g. <code>IQueryable&lt;T&gt;.Where</code>, while the former does not; I have tried this in the immediate window of the debugger.</p> <p>I should also mention that when the predicate is <code>w =&gt; true</code>, 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.</p> <h2>Final Solution</h2> <p>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 <a href="https://stackoverflow.com/a/10467736/50079">separate answer</a> to keep this long question from becoming even longer.</p> <p>Thanks to everyone for your answers and comments!</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload