将谓词的expression式树变为另一种types

介绍

在我正在开发的应用程序中,每个业务对象有两种:“ActiveRecord”类和“DataContract”类。 所以例如,会有:

namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } } 

数据库访问层负责在系列之间进行转换:可以告诉它更新一个DataContract.Widget ,它会奇迹般地创build一个具有相同属性值的ActiveRecord.Widget ,并保存它。

尝试重构此数据库访问层时,问题浮出水面。

问题

我想添加如下的方法到数据库访问层:

 // Widget is DataContract.Widget interface IDbAccessLayer { IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate); } 

以上是一个简单的通用的自定义谓词的“get”方法。 唯一感兴趣的是我传递一个expression式树而不是一个lambda,因为在IDbAccessLayer我正在查询一个IQueryable<ActiveRecord.Widget> ; 有效地做到这一点(认为LINQ到SQL)我需要传递一个expression式树,所以这个方法要求这样做。

snag:该参数需要从Expression<Func<DataContract.Widget, bool>>魔术般地转换为Expression<Func<ActiveRecord.Widget, bool>>

尝试解决scheme

我想在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; 

…expression式树包含一个MemberAccessExpression实例,该实例具有MemberInfotypes的属性,用于描述DataContract.Widget.Id 。 在expression式树和参数集合( predicate.Parameters )中都有ParameterExpression实例,它们描述DataContract.Widget ; 所有这些都会导致错误,因为可查询主体不包含该types的小部件,而是ActiveRecord.Widget

search了一下之后,我发现了System.Linq.Expressions.ExpressionVisitor (它的源代码可以在这里find一个方法),它提供了一个方便的方法来修改一个expression式树。 在.NET 4中,这个类是开箱即用的。

有了这个武装,我实现了一个访问者。 这个简单的访问者只需要改变成员访问和参数expression式中的types,但是这足以使用谓词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构造得很好。 坏消息是它不工作, 当我尝试使用它的时候它会炸毁我,而exception消息根本就没有帮助。

我已经检查了我的代码生成的lambdaexpression式和具有相同expression式的硬编码lambdaexpression式; 他们看起来完全一样。 我花了几个小时在debugging器上试图找出一些区别,但我不能。

当谓词是w => w.Id == 0lambda看起来就像referenceLambda 。 但后者适用于例如IQueryable<T>.Where ,前者不是; 我已经在debugging器的直接窗口中尝试过了。

我还应该提到,当谓词w => true ,一切正常。 所以我假设我在访问者方面做得不够多,但是我找不到更多的关注点。

最终解决scheme

考虑到问题的正确答案(其中两个以下;一个简短,一个代码),问题就解决了; 我把代码和几个重要的注释放在一个单独的答案中,以防止这个长问题变得更长。

感谢大家的回答和评论!

看来你是在VisitMember()这里产生两次参数expression式:

 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声明的实例相同的实例(不只是相同的types和名称)。 我之前遇到过这种情况,但是我认为结果是我无法创buildexpression式,只会抛出一个exception。 在任何情况下,你可以尝试重用参数实例,看看是否有帮助。

事实certificate,棘手的部分是,新的lambdaexpression式树中存在的ParameterExpression实例必须与Expression.LambdaIEnumerable<ParameterExpression>参数中传递的实例相同

请注意,在TransformPredicateLambda我将t => typeof(TNewTarget)作为“types转换器”函数; 那是因为在这个特定的情况下,我们可以假定所有的参数和成员访问都是那个特定的types。 更高级的场景可能需要额外的逻辑。

代码:

 internal class DbAccessLayer { private static Expression<Func<TNewTarget, bool>> TransformPredicateLambda<TOldTarget, TNewTarget>( Expression<Func<TOldTarget, bool>> predicate) { var lambda = (LambdaExpression) predicate; if (lambda == null) { throw new NotSupportedException(); } var mutator = new ExpressionTargetTypeMutator(t => typeof(TNewTarget)); var explorer = new ExpressionTreeExplorer(); var converted = mutator.Visit(predicate.Body); return Expression.Lambda<Func<TNewTarget, bool>>( converted, lambda.Name, lambda.TailCall, explorer.Explore(converted).OfType<ParameterExpression>()); } private class ExpressionTargetTypeMutator : ExpressionVisitor { private readonly Func<Type, Type> typeConverter; public ExpressionTargetTypeMutator(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); } } } /// <summary> /// Utility class for the traversal of expression trees. /// </summary> public class ExpressionTreeExplorer { private readonly Visitor visitor = new Visitor(); /// <summary> /// Returns the enumerable collection of expressions that comprise /// the expression tree rooted at the specified node. /// </summary> /// <param name="node">The node.</param> /// <returns> /// The enumerable collection of expressions that comprise the expression tree. /// </returns> public IEnumerable<Expression> Explore(Expression node) { return this.visitor.Explore(node); } private class Visitor : ExpressionVisitor { private readonly List<Expression> expressions = new List<Expression>(); protected override Expression VisitBinary(BinaryExpression node) { this.expressions.Add(node); return base.VisitBinary(node); } protected override Expression VisitBlock(BlockExpression node) { this.expressions.Add(node); return base.VisitBlock(node); } protected override Expression VisitConditional(ConditionalExpression node) { this.expressions.Add(node); return base.VisitConditional(node); } protected override Expression VisitConstant(ConstantExpression node) { this.expressions.Add(node); return base.VisitConstant(node); } protected override Expression VisitDebugInfo(DebugInfoExpression node) { this.expressions.Add(node); return base.VisitDebugInfo(node); } protected override Expression VisitDefault(DefaultExpression node) { this.expressions.Add(node); return base.VisitDefault(node); } protected override Expression VisitDynamic(DynamicExpression node) { this.expressions.Add(node); return base.VisitDynamic(node); } protected override Expression VisitExtension(Expression node) { this.expressions.Add(node); return base.VisitExtension(node); } protected override Expression VisitGoto(GotoExpression node) { this.expressions.Add(node); return base.VisitGoto(node); } protected override Expression VisitIndex(IndexExpression node) { this.expressions.Add(node); return base.VisitIndex(node); } protected override Expression VisitInvocation(InvocationExpression node) { this.expressions.Add(node); return base.VisitInvocation(node); } protected override Expression VisitLabel(LabelExpression node) { this.expressions.Add(node); return base.VisitLabel(node); } protected override Expression VisitLambda<T>(Expression<T> node) { this.expressions.Add(node); return base.VisitLambda(node); } protected override Expression VisitListInit(ListInitExpression node) { this.expressions.Add(node); return base.VisitListInit(node); } protected override Expression VisitLoop(LoopExpression node) { this.expressions.Add(node); return base.VisitLoop(node); } protected override Expression VisitMember(MemberExpression node) { this.expressions.Add(node); return base.VisitMember(node); } protected override Expression VisitMemberInit(MemberInitExpression node) { this.expressions.Add(node); return base.VisitMemberInit(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { this.expressions.Add(node); return base.VisitMethodCall(node); } protected override Expression VisitNew(NewExpression node) { this.expressions.Add(node); return base.VisitNew(node); } protected override Expression VisitNewArray(NewArrayExpression node) { this.expressions.Add(node); return base.VisitNewArray(node); } protected override Expression VisitParameter(ParameterExpression node) { this.expressions.Add(node); return base.VisitParameter(node); } protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) { this.expressions.Add(node); return base.VisitRuntimeVariables(node); } protected override Expression VisitSwitch(SwitchExpression node) { this.expressions.Add(node); return base.VisitSwitch(node); } protected override Expression VisitTry(TryExpression node) { this.expressions.Add(node); return base.VisitTry(node); } protected override Expression VisitTypeBinary(TypeBinaryExpression node) { this.expressions.Add(node); return base.VisitTypeBinary(node); } protected override Expression VisitUnary(UnaryExpression node) { this.expressions.Add(node); return base.VisitUnary(node); } public IEnumerable<Expression> Explore(Expression node) { this.expressions.Clear(); this.Visit(node); return expressions.ToArray(); } } } 

我尝试了简单的(不完整的)实现来改变expression式p => p.Id == 15 (代码如下)。 有一个名为“CrossMapping”的类定义了原始types和“新”types和types成员之间的映射。

对于每个expression式types,有几个名为Mutate_XY_Expression metods,这使得新的突变expression式成为可能。 方法input需要原始expression式( MemberExpression originalExpression )作为expression式的模型,列表或参数expression式( IList<ParameterExpression> parameterExpressions )是由“父”expression式定义的参数,应由“父”的主体使用,映射对象( CrossMapping mapping ),它定义了types和成员之间的映射。

对于完整的实现,您可能需要比父参数更多的参数信息。 但是模式应该是一样的。

如你所知,示例不实现访问者模式 – 这是因为简单。 但是转化为他们是没有障碍的。

我希望,这将有所帮助。

代码(C#4.0):

 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Linq.Expressions; namespace ConsoleApplication1 { public class Product1 { public int Id { get; set; } public string Name { get; set; } public decimal Weight { get; set; } } public class Product2 { public int Id { get; set; } public string Name { get; set; } public decimal Weight { get; set; } } class Program { static void Main( string[] args ) { // list of products typed as Product1 var lst1 = new List<Product1> { new Product1{ Id = 1, Name = "One" }, new Product1{ Id = 15, Name = "Fifteen" }, new Product1{ Id = 9, Name = "Nine" } }; // the expression for filtering products // typed as Product1 Expression<Func<Product1, bool>> q1; q1 = p => p.Id == 15; // list of products typed as Product2 var lst2 = new List<Product2> { new Product2{ Id = 1, Name = "One" }, new Product2{ Id = 15, Name = "Fifteen" }, new Product2{ Id = 9, Name = "Nine" } }; // type of Product1 var tp1 = typeof( Product1 ); // property info of "Id" property from type Product1 var tp1Id = tp1.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance ); // delegate type for predicating for Product1 var tp1FuncBool = typeof( Func<,> ).MakeGenericType( tp1, typeof( bool ) ); // type of Product2 var tp2 = typeof( Product2 ); // property info of "Id" property from type Product2 var tp21Id = tp2.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance ); // delegate type for predicating for Product2 var tp2FuncBool = typeof( Func<,> ).MakeGenericType( tp2, typeof( bool ) ); // mapping object for types and type members var cm1 = new CrossMapping { TypeMapping = { // Product1 -> Product2 { tp1, tp2 }, // Func<Product1, bool> -> Func<Product2, bool> { tp1FuncBool, tp2FuncBool } }, MemberMapping = { // Product1.Id -> Product2.Id { tp1Id, tp21Id } } }; // mutate express from Product1's "enviroment" to Product2's "enviroment" var cq1_2 = MutateExpression( q1, cm1 ); // compile lambda to delegate var dlg1_2 = ((LambdaExpression)cq1_2).Compile(); // executing delegate var rslt1_2 = lst2.Where( (Func<Product2, bool>)dlg1_2 ).ToList(); return; } class CrossMapping { public IDictionary<Type, Type> TypeMapping { get; private set; } public IDictionary<MemberInfo, MemberInfo> MemberMapping { get; private set; } public CrossMapping() { this.TypeMapping = new Dictionary<Type, Type>(); this.MemberMapping = new Dictionary<MemberInfo, MemberInfo>(); } } static Expression MutateExpression( Expression originalExpression, CrossMapping mapping ) { var ret = MutateExpression( originalExpression: originalExpression, parameterExpressions: null, mapping: mapping ); return ret; } static Expression MutateExpression( Expression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { Expression ret; if ( null == originalExpression ) { ret = null; } else if ( originalExpression is LambdaExpression ) { ret = MutateLambdaExpression( (LambdaExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is BinaryExpression ) { ret = MutateBinaryExpression( (BinaryExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is ParameterExpression ) { ret = MutateParameterExpression( (ParameterExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is MemberExpression ) { ret = MutateMemberExpression( (MemberExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is ConstantExpression ) { ret = MutateConstantExpression( (ConstantExpression)originalExpression, parameterExpressions, mapping ); } else { throw new NotImplementedException(); } return ret; } static Type MutateType( Type originalType, IDictionary<Type, Type> typeMapping ) { if ( null == originalType ) { return null; } Type ret; typeMapping.TryGetValue( originalType, out ret ); if ( null == ret ) { ret = originalType; } return ret; } static MemberInfo MutateMember( MemberInfo originalMember, IDictionary<MemberInfo, MemberInfo> memberMapping ) { if ( null == originalMember ) { return null; } MemberInfo ret; memberMapping.TryGetValue( originalMember, out ret ); if ( null == ret ) { ret = originalMember; } return ret; } static LambdaExpression MutateLambdaExpression( LambdaExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newParameters = (from p in originalExpression.Parameters let np = MutateParameterExpression( p, parameterExpressions, mapping ) select np).ToArray(); var newBody = MutateExpression( originalExpression.Body, newParameters, mapping ); var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var ret = Expression.Lambda( delegateType: newType, body: newBody, name: originalExpression.Name, tailCall: originalExpression.TailCall, parameters: newParameters ); return ret; } static BinaryExpression MutateBinaryExpression( BinaryExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newExprConversion = MutateExpression( originalExpression.Conversion, parameterExpressions, mapping ); var newExprLambdaConversion = (LambdaExpression)newExprConversion; var newExprLeft = MutateExpression( originalExpression.Left, parameterExpressions, mapping ); var newExprRigth = MutateExpression( originalExpression.Right, parameterExpressions, mapping ); var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var newMember = MutateMember( originalExpression.Method, mapping.MemberMapping); var newMethod = (MethodInfo)newMember; var ret = Expression.MakeBinary( binaryType: originalExpression.NodeType, left: newExprLeft, right: newExprRigth, liftToNull: originalExpression.IsLiftedToNull, method: newMethod, conversion: newExprLambdaConversion ); return ret; } static ParameterExpression MutateParameterExpression( ParameterExpression originalExpresion, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpresion ) { return null; } ParameterExpression ret = null; if ( null != parameterExpressions ) { ret = (from p in parameterExpressions where p.Name == originalExpresion.Name select p).FirstOrDefault(); } if ( null == ret ) { var newType = MutateType( originalExpresion.Type, mapping.TypeMapping ); ret = Expression.Parameter( newType, originalExpresion.Name ); } return ret; } static MemberExpression MutateMemberExpression( MemberExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newExpression = MutateExpression( originalExpression.Expression, parameterExpressions, mapping ); var newMember = MutateMember( originalExpression.Member, mapping.MemberMapping ); var ret = Expression.MakeMemberAccess( expression: newExpression, member: newMember ); return ret; } static ConstantExpression MutateConstantExpression( ConstantExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var newValue = originalExpression.Value; var ret = Expression.Constant( value: newValue, type: newType ); return ret; } } } 

乔恩自己的答案很好,所以我扩展它来处理方法调用,常量expression式等,所以现在它也适用于expression式,如:

 x => x.SubObjects .AsQueryable() .SelectMany(y => y.GrandChildObjects) .Any(z => z.Value == 3) 

由于我们唯一需要的是ParameterExpressions,所以我也放弃了ExpressionTreeExplorer

代码如下( 更新:完成转换后清除caching

 public class ExpressionTargetTypeMutator : ExpressionVisitor { private readonly Func<Type, Type> typeConverter; private readonly Dictionary<Expression, Expression> _convertedExpressions = new Dictionary<Expression, Expression>(); public ExpressionTargetTypeMutator(Func<Type, Type> typeConverter) { this.typeConverter = typeConverter; } // Clear the ParameterExpression cache between calls to Visit. // Not thread safe, but you can probably fix it easily. public override Expression Visit(Expression node) { bool outermostCall = false; if (false == _isVisiting) { this._isVisiting = true; outermostCall = true; } try { return base.Visit(node); } finally { if (outermostCall) { this._isVisiting = false; _convertedExpressions.Clear(); } } } protected override Expression VisitMember(MemberExpression node) { var sourceType = node.Member.ReflectedType; var targetType = this.typeConverter(sourceType); var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), targetType.GetProperty(node.Member.Name)); return converted; } protected override Expression VisitParameter(ParameterExpression node) { Expression converted; if (false == _convertedExpressions.TryGetValue(node, out converted)) { var sourceType = node.Type; var targetType = this.typeConverter(sourceType); converted = Expression.Parameter(targetType, node.Name); _convertedExpressions.Add(node, converted); } return converted; } protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.IsGenericMethod) { var convertedTypeArguments = node.Method.GetGenericArguments() .Select(this.typeConverter) .ToArray(); var genericMethodDefinition = node.Method.GetGenericMethodDefinition(); var newMethod = genericMethodDefinition.MakeGenericMethod(convertedTypeArguments); return Expression.Call(newMethod, node.Arguments.Select(this.Visit)); } return base.VisitMethodCall(node); } protected override Expression VisitConstant(ConstantExpression node) { var valueExpression = node.Value as Expression; if (null != valueExpression) { return Expression.Constant(this.Visit(valueExpression)); } return base.VisitConstant(node); } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda(this.Visit(node.Body), node.Name, node.TailCall, node.Parameters.Select(x => (ParameterExpression)this.VisitParameter(x))); } } 

不ExecuteTypedList完成你想要做什么? SubSonic将填充您的DTO / POCO。 来自Rob Connery的博客:

ExecuteTypedList <>尝试将返回列的名称与传入types的属性的名称进行匹配。 在这个例子中,它们完全匹配 – 那不是完全真实的世界。 你可以通过对列进行别名来解决这个问题 – 就像别人用SQL调用一样:

 return Northwind.DB.Select("ProductID as 'ID'", "ProductName as 'Name'", "UnitPrice as 'Price'") .From<Northwind.Product>().ExecuteTypedList<Product>(); 

这里是Rob的书写分离,可testing的代码与SubSonic 2.1的链接

我认为如果你正确地做了你的查询的话,Linq-To-Sql会产生理想的SQL。 在这种情况下,使用IQueryable和延期执行可以避免返回所有ActiveRecord.Widgetlogging。

 IEnumerable<DataContract.Widget> GetMany( Func<DataContract.Widget, bool> predicate) { // get Widgets IQueryable<DataContract.Widget> qry = dc.Widgets.Select(w => TODO: CONVERT_TO_DataContract.Widget); return qry.Where(predicate); }