一个变异predicate的前pression树的目标另一种类型 [英] Mutating the expression tree of a predicate to target another type

查看:121
本文介绍了一个变异predicate的前pression树的目标另一种类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我目前工作的应用程序,有两种每个业务对象:以ActiveRecord的类和DataContract的一种。因此,例如,将有:

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; }
    }
}

数据库访问层需要家庭之间转换的护理:你可以告诉它更新 DataContract.Widget ,它会奇迹般地创建一个的ActiveRecord .Widget 具有相同的属性值并保存替代。

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.

我要添加类似下面的数据库访问层的方法:

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);
}

以上是一个简单的一般用途的得的方法定制predicate。唯一关注的一点是,我在离pression树正在传递一个lambda,而不是因为里面的 IDbAccessLayer 我查询了的IQueryable&LT; ActiveRecord.Widget&GT; ;要做到这一点有效的(个人认为LINQ到SQL)我需要在离pression树传递所以这种方法要求这一点。

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.

的障碍:该参数需要从防爆pression&LT神奇地转化; Func键&LT; D​​ataContract.Widget,布尔&GT;&GT; 防爆pression&LT;&Func键LT; ActiveRecord.Widget,布尔&GT;方式&gt;

The snag: the parameter needs to be magically transformed from an Expression<Func<DataContract.Widget, bool>> to an Expression<Func<ActiveRecord.Widget, bool>>.

我想里面有什么 GetMany 做的是:

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;

...恩pression树包含有类型的属性 MemberAccessEx pression 实例的MemberInfo 描述 DataContract.Widget.Id
还有无论是在除权pression树和它的参数集合 ParameterEx pression 实例( predicate.Parameters )来说明 DataContract.Widget ;所有这一切都将导致错误,因为在可查询的身体不包含类型的widget而是 ActiveRecord.Widget

...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.

搜索了一下后,我发现<一个href=\"http://msdn.microsoft.com/en-us/library/system.linq.ex$p$pssions.ex$p$pssionvisitor%28v=VS.100%29.aspx\"><$c$c>System.Linq.Ex$p$pssions.Ex$p$pssionVisitor (它的来源可以在这里找到 在然而上下文到),它提供了一个便捷的方式来修改前pression树。在.NET 4中,本类包含开箱即用的。

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.

这个武装,我实现了一个客人。这个简单的游客只需要改变成员访问和参数前pressions类型的照顾,但这是不够的功能与predicate工作 W =&GT; w.Id == 0

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);
    }
}

通过这个客人, GetMany 变成了:

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.

我已经检查了我的code产生拉姆达与同前pression硬codeD的lambda;他们看起来完全一样。我花了几个小时的调试器试图找到一些差异,但我做不到。

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.

当predicate是 W =&GT; w.Id == 0 的λ看起来很像 referenceLambda 。但后者的作品与例如的IQueryable&LT; T&GT;。凡,而前者没有;我已经在调试器的即时窗口尝试这个。

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.

我还要提到的是,当predicate是 W =&GT;真正,一切工作正常。所以我假设我不是做足够的工作在访问者,但我找不到任何更多的线索可循。

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.

考虑到正确答案的问题后(​​二低于其中;一短,一用code)该问题得到解决;我把code。与在单独回答几个重要事项一起把这个长的问题变得更长时间

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!

推荐答案

看来你正在生成的参数e pression两次,VisitMember()位置:

It seems you're generating the parameter expression twice, in VisitMember() here:

var converted = Expression.MakeMemberAccess(
    base.Visit(node.Expression),
    activeRecordType.GetProperty(node.Member.Name));

...因为base.Visit()将在VisitParameter最终我想象,在GetMany()本身:

...since base.Visit() will end up in VisitParameter I imagine, and in GetMany() itself:

var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
    visitor.Visit(predicate.Body),
    predicate.Parameters.Select(p => visitor.Visit(p));

如果你在身体使用ParameterEx pression,它必须是相同的实例(而不仅仅是相同类型和名称)作为一个申报拉姆达。
我有问题之前,这种情况下,虽然我认为结果是,我只是没有能够创造出前pression,它只是抛出一个异常。在任何情况下,你可以尝试重用参数实例,看看是否有帮助。

If you're using a ParameterExpression in the body, it has to be the same instance (not just the same type and name) as the one declared for the Lambda. I've had problems before with this kind of scenario, though I think the result was that I just wasn't able to create the expression, it would just throw an exception. In any case you might try reusing the parameter instance see if it helps.

这篇关于一个变异predicate的前pression树的目标另一种类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆