改变谓词的表达式树以针对另一种类型

Posted

技术标签:

【中文标题】改变谓词的表达式树以针对另一种类型【英文标题】:Mutating the expression tree of a predicate to target another type 【发布时间】:2011-02-17 08:13:59 【问题描述】:

简介

在我目前正在处理的应用程序中,每个业务对象有两种类型:“ActiveRecord”类型和“DataContract”类型。例如,会有:

namespace ActiveRecord 
    class Widget 
        public int Id  get; set; 
    


namespace DataContract 
    class Widget 
        public int Id  get; set; 
    

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

在尝试重构此数据库访问层时出现问题。

问题

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

// Widget is DataContract.Widget

interface IDbAccessLayer 
    IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate);

以上是带有自定义谓词的简单通用“get”方法。唯一感兴趣的是我传入的是表达式树而不是 lambda,因为在 IDbAccessLayer 内部我正在查询 IQueryable&lt;ActiveRecord.Widget&gt;;为了有效地做到这一点(想想 LINQ to SQL),我需要传入一个表达式树,所以这个方法只需要这个。

障碍:参数需要神奇地从Expression&lt;Func&lt;DataContract.Widget, bool&gt;&gt; 转换为Expression&lt;Func&lt;ActiveRecord.Widget, bool&gt;&gt;

尝试的解决方案

我想在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 实例,该实例具有一个描述DataContract.Widget.IdMemberInfo 类型的属性。 在表达式树及其参数集合 (predicate.Parameters) 中也有 ParameterExpression 实例,这些实例描述了 DataContract.Widget;所有这些都会导致错误,因为可查询正文不包含该类型的小部件,而是包含ActiveRecord.Widget

经过一番搜索,我找到了System.Linq.Expressions.ExpressionVisitor(它的来源可以在操作指南的上下文中找到here),它提供了一种修改表达式树的便捷方法。在 .NET 4 中,这个类是开箱即用的。

有了这个,我实现了一个访问者。这个简单的访问者只负责更改成员访问和参数表达式中的类型,但这对于使用谓词 w =&gt; 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;它们看起来完全一样。我在调试器中花了几个小时试图找到一些不同之处,但我做不到。

当谓词为w =&gt; w.Id == 0 时,lambda 看起来与referenceLambda 完全相同。但后者适用于例如IQueryable&lt;T&gt;.Where,而前者没有;我已经在调试器的即时窗口中尝试过了。

我还应该提到,当谓词是w =&gt; true 时,一切正常。因此,我假设我没有在访问者中做足够的工作,但我找不到更多的线索。

最终解决方案

在考虑了问题的正确答案(下面两个;一个简短,一个带代码)后,问题解决了;我将代码连同一些重要说明放在separate answer 中,以防止这个冗长的问题变得更长。

感谢大家的回答和cmets!

【问题讨论】:

我能问一下为什么您要实现自己的数据访问层,而不是使用或改进已经存在的众多开源解决方案之一吗? “ActiveRecord”部分是 SubSonic(它看起来很整洁,但如果您不使用 MSSQL,请不要打扰)。 “DataContract”部分是必要的,因为我们需要使用可以根据目的进行调整的业务对象(SubSonic 生成代码;生成的代码和自定义调整不混合)。两者之间的“翻译”是必要的邪恶。当然这一切都是题外话…… 您是否收到 InvalidOperationException 消息“从范围 '' 引用的 'ConsoleApplication1.Product2' 类型的变量 'w',但未定义”或其他? 这是我在查询提供程序是 LINQ to Objects 时得到的。当它是 LINQ to SQL 时,它是不同的(SubSonic 抛出 NotSupportedException),并且在完全错误的地方“检测到”错误。 SubSonic 源代码的相关部分是将w =&gt; w.Name.Length 等属性访问转换为SELECT CHAR_LENGTH(Name) -- 类SubSonic.Linq.Translation.mysql.MySqlFormatter @Lorenzo:只需删除对它们的任何引用;您将使用Expression.Lambda 中的this overload,其中(逻辑上)不采用这些参数。 【参考方案1】:

您似乎在 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 声明的实例相同的实例(不仅仅是相同的类型和名称)。 我以前遇到过这种情况的问题,尽管我认为结果是我无法创建表达式,它只会抛出异常。在任何情况下,您都可以尝试重用参数实例,看看是否有帮助。

【讨论】:

您的答案看起来很有希望,因为简单的w =&gt; w.Id == 0 确实在正文中使用了ParameterExpression,我完全不认为它们可能需要是同一个实例(WTF?文档? ??)。我会调查并回复你。谢谢! @diegov - +1 - 我认为这是错误的。然而,我要做的是在构造时将新的 ParameterExpression 输入到访问者中,并且从不在访问者内部构造它。每当它找到“旧”参数的实例时,就会用新的参数替换它。 @diegov - 确认:这就是问题所在!谢谢!出于好奇:你怎么知道它必须是同一个实例? :-) 我刚刚检查过,实际上主要是运气,问题在于编译表达式,而不仅仅是我错误回忆的构建它,而且我认为 subsonic 不会编译它。但它可能正在假设它是同一个实例,这可能是神秘异常的原因.. 运气与否,这是一个非常敏锐的观察!你是对的,SubSonic 没有编译表达式——而是将它拆开来构建 SQL,这就是重点。【参考方案2】:

事实证明,棘手的部分只是新 lambda 表达式树中存在的 ParameterExpression 实例必须与在 IEnumerable&lt;ParameterExpression&gt; 参数中传递的 相同实例 Expression.Lambda.

请注意,在TransformPredicateLambda 内部,我将t =&gt; typeof(TNewTarget) 作为“类型转换器”功能;这是因为在这种特定情况下,我们可以假设所有参数和成员访问都属于该特定类型。更高级的场景可能需要额外的逻辑。

代码:

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

【讨论】:

我认为ExpressionTargetTypeMutator 类中有错字。事实上,基类中没有 VisitMember 方法可以覆盖。此外,我已经实现了一个完全按原样使用您的代码的解决方案,并发现了一个小问题。如果我的表达式是w =&gt; w.Code == 1,那么一切都可以正常工作,但如果我有w =&gt; w.Code == tmpVar,其中tmpVar 的值为1,则它不起作用。是正确的还是只是我混淆了某些东西?附:无论如何,很棒的代码,谢谢分享! @Lorenzo:.NET 4 类上有一个VisitMember。实际上,真正的最终解决方案比我在此处发布的更多,但它的时间更长且涉及更多。此版本不适用于捕获的变量。 @Lorenzo:我想那里叫VisitMemberAccess 是的,你是对的。我的意思是关于使它与捕获的变量一起工作的建议:) @Lorenzo:你会想阅读this。【参考方案3】:

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

每个表达式类型都有几个名为 Mutate_XY_Expression 的方法,这会产生新的变异表达式。方法输入需要原始表达式(MemberExpression originalExpression)作为表达式模型,列表或参数表达式(IList&lt;ParameterExpression&gt; parameterExpressions)是由“父”表达式定义的参数,应该由“父”体使用,以及映射对象(CrossMapping mapping),它定义了类型和成员之间的映射。

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

如您所知,Sample 没有实现访问者模式 - 这是因为简单。但是转换为他们没有障碍。

希望对你有帮助。

代码(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;
        
    

【讨论】:

感谢您花时间编写自己的宠物程序来说明。遗憾的是,我不能同时接受你和 diegov 作为答案...... diegov 是第一个并且死了,所以我接受了他的。如果可以的话,我会更多地支持你们:) 这对 C# 3.0 也有效吗? @David:如果您将方法调用中的命名参数 (var ret = MutateExpression( originalExpression: originalExpression, parameterExpressions: null, mapping: mapping );) 替换为 (var ret = MutateExpression( originalExpression, null , mapping );),那么就可以在 C# 3.0 中使用了,【参考方案4】:

上面的Jon's own answer 很好,所以我将它扩展为处理方法调用、常量表达式等,现在它也适用于以下表达式:

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

我也取消了ExpressionTreeExplorer,因为我们唯一需要的是 ParameterExpressions。

这是代码(更新:完成转换后清除缓存

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

【讨论】:

我尝试将您的代码调整为 .NET 3.5,最终只删除了 VisitLambda&lt;T&gt;() 方法。在执行此操作时,我发现如果原始表达式与此类似,例如x =&gt; x.Property1.Property2.Property3 == 1,则此代码也像已接受的答案一样会遇到问题。 MakeMemberAccess() 方法由于其 typeConverter 成员变量而尝试将“导航”类型(例如 Property2)转换为原始类型时,会出现此问题。由于我的表达能力不是很强,请您帮我解决这个问题吗?【参考方案5】:

ExecuteTypedList 没有完成你想做的事情吗? SubSonic 将填充您的 DTO/POCO。来自 Rob Connery 的博客:

ExecuteTypedList 尝试匹配 返回的列的名称 属性的名称 传入的类型。在这个例子中,他们 完全匹配 - 那不是 完全真实的世界。你可以得到 通过给列加上别名来解决这个问题 - 就像你给 SQL 起别名一样 调用:

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

这是 Rob 的 Writing Decoupled, Testable code with SubSonic 2.1 的链接

【讨论】:

ExecuteTypedList&lt;&gt; 仅存在于 SubSonic 2 中,我正在使用 3。SS2 甚至没有 lambdas... 顺便说一句,您建议的解决方案需要 (a) 我的代码库是重新访问和一些(但不是全部;有时我们在我们需要的全部结果集上使用 LINQ to Objects)Func 表达式更改为 Func,并且 (b) 我们放弃任何自定义“变异”逻辑和拥有它的选项。我不只是想在这里编译一个语句。 好的 - 我想我明白了。本质上,您想编写如下内容:DataContract.WidgetList().AsQueryable().Where(predicate),其谓词类型为 DataContract,但已转换为 ActiveRecord。 总结得很好,是的。【参考方案6】:

我认为如果您正确执行查询,Linq-To-Sql 将产生理想的 SQL。在这种情况下,使用IQueryable 和延迟执行可以避免返回所有ActiveRecord.Widget 记录。

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

【讨论】:

对不起,这个答案既无用又错误。我不确定你想表达什么,但问题显然是关于表达式树的,你的代码中甚至没有一半。 @Jon,我认为你的主要目标是解决你的问题!!!我建议的是一种不同的方法,它不使用表达式树,因为它从一开始就避免了性能问题。请确认这种情况下生成的SQL,您会看到只返回了想要的记录。 我仍然看不到您要提出的建议。据我了解,您的代码显示“使用此谓词过滤 DataContract.Widget 的可查询集合”。问题(1):DataContract.Widget 不存在IQueryProvider。问题(2):即使我们写了一个,它会如何实现? (a) 通过转换查询表达式并查询ActiveRecord.Widget? [问题(3):您的代码不采用查询表达式,而是采用 lambda] 这就是我正在尝试做的事情!或(b)通过抓取所有记录,转换它们并随后过滤?这是我要避免的。 我的代码说:(1)获取ActiveRecord.Widget记录并进行转换(将它们转换为DataContract.Widget)。 (2) 将所需的过滤器应用于转换后的IQueryable。虽然看起来您正在执行 2 次操作,但实际上,Linq-to-SQL 将只使用一次查询来对数据库进行转换和过滤。因此,只返回所需的记录。 我认为您的第一步实际上会提取所有记录,但我承认我不知道这是事实。但是,另一个问题仍然存在:我没有 IQueryable&lt;DataContract.Widget&gt; 实现。

以上是关于改变谓词的表达式树以针对另一种类型的主要内容,如果未能解决你的问题,请参考以下文章

构建动态表达式树以过滤集合属性

如何创建表达式树以执行与“StartsWith”相同的操作

是否可以解释 C# 表达式树以发出 JavaScript?

表达式树 Expression Trees

[C#] 说说表达式树 - Expression Trees

加载程序集并在另一个应用程序域中对其类型应用谓词