在我当前正在处理的应用程序中,每种业务对象都有两种:“ ActiveRecord”类型和“ DataContract”类型。因此,例如,将有:
namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } }
数据库访问层负责在族之间进行转换:您可以告诉它更新a DataContract.Widget,它将神奇地创建一个ActiveRecord.Widget具有相同属性值的an 并将其保存。
DataContract.Widget
ActiveRecord.Widget
尝试重构此数据库访问层时出现了问题。
我想将以下方法添加到数据库访问层:
// Widget is DataContract.Widget interface IDbAccessLayer { IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate); }
上面是带有自定义谓词的简单的通用“ get”方法。唯一感兴趣的一点是,我传递的是表达式树而不是lambda,因为在内部IDbAccessLayer我正在查询IQueryable<ActiveRecord.Widget>; 为了有效地做到这一点(想想LINQ to SQL),我需要传递一个表达式树,这样该方法就可以做到这一点。
IDbAccessLayer
IQueryable<ActiveRecord.Widget>
障碍:需要将参数从神奇地转换Expression<Func<DataContract.Widget, bool>>为Expression<Func<ActiveRecord.Widget, bool>>。
Expression<Func<DataContract.Widget, bool>>
Expression<Func<ActiveRecord.Widget, bool>>
我想在里面做的GetMany是:
GetMany
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 }
这将不起作用,因为在典型情况下,例如:
predicate == w => w.Id == 0;
…表达式树包含一个MemberAccessExpression实例,该实例的属性类型MemberInfo为describe DataContract.Widget.Id。ParameterExpression在表达式树及其参数集合(predicate.Parameters)中也有实例描述DataContract.Widget; 所有这些都会导致错误,因为可查询主体不包含该类型的小部件,而是ActiveRecord.Widget。
MemberAccessExpression
MemberInfo
DataContract.Widget.Id
ParameterExpression
predicate.Parameters
搜索了一下后,我发现System.Linq.Expressions.ExpressionVisitor(其来源可以发现这里的如何做的情况下),它提供了一个方便的方式来修改表达式树。在.NET 4中,该类是开箱即用的。
System.Linq.Expressions.ExpressionVisitor
有了这个,我实现了一个访客。这个简单的访问者只需要更改成员访问和参数表达式中的类型,但这足以使用谓词w => w.Id == 0。
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将成为:
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. }
好消息是,lambda它的构造很好。坏消息是它不起作用。当我尝试使用它时,它就爆炸了,异常消息真的根本没有帮助。
lambda
我检查了我的代码产生的lambda和具有相同表达式的硬编码lambda。他们看起来完全一样。我在调试器中花了几个小时试图找到一些区别,但是我做不到。
当谓词为时w => w.Id == 0,lambda看起来完全像referenceLambda。但是后者适用于例如IQueryable<T>.Where,而前者则不适用;我已经在调试器的即时窗口中尝试过此操作。
referenceLambda
IQueryable<T>.Where
我还应该提到,当谓词为时w => true,一切正常。因此,我假设我没有在访问者中做足够的工作,但是我找不到更多的线索可循。
w => true
在考虑了问题的正确答案之后(以下两个);一个简短的问题,一个带有代码的问题得以解决;我将代码和一些重要说明放在一个单独的答案中,以防止这个漫长的问题变得更长。
感谢大家的回答和评论!
似乎您在VisitMember()中两次生成了参数表达式:
var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), activeRecordType.GetProperty(node.Member.Name));
…因为base.Visit()将以我想象的VisitParameter和GetMany()本身结尾:
var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>( visitor.Visit(predicate.Body), predicate.Parameters.Select(p => visitor.Visit(p));
如果您在主体中使用ParameterExpression,则它必须与为Lambda声明的实例具有相同的实例(不仅仅是相同的类型和名称)。在这种情况下,我曾经遇到过问题,尽管我认为结果是我无法创建表达式,但它只会引发异常。无论如何,您都可以尝试重用参数实例,以查看是否有帮助。