改变谓词的表达式树以针对另一种类型
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<ActiveRecord.Widget>
;为了有效地做到这一点(想想 LINQ to SQL),我需要传入一个表达式树,所以这个方法只需要这个。
障碍:参数需要神奇地从Expression<Func<DataContract.Widget, bool>>
转换为Expression<Func<ActiveRecord.Widget, bool>>
。
尝试的解决方案
我想在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.Id
的MemberInfo
类型的属性。
在表达式树及其参数集合 (predicate.Parameters
) 中也有 ParameterExpression
实例,这些实例描述了 DataContract.Widget
;所有这些都会导致错误,因为可查询正文不包含该类型的小部件,而是包含ActiveRecord.Widget
。
经过一番搜索,我找到了System.Linq.Expressions.ExpressionVisitor
(它的来源可以在操作指南的上下文中找到here),它提供了一种修改表达式树的便捷方法。在 .NET 4 中,这个类是开箱即用的。
有了这个,我实现了一个访问者。这个简单的访问者只负责更改成员访问和参数表达式中的类型,但这对于使用谓词 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;它们看起来完全一样。我在调试器中花了几个小时试图找到一些不同之处,但我做不到。
当谓词为w => w.Id == 0
时,lambda
看起来与referenceLambda
完全相同。但后者适用于例如IQueryable<T>.Where
,而前者没有;我已经在调试器的即时窗口中尝试过了。
我还应该提到,当谓词是w => true
时,一切正常。因此,我假设我没有在访问者中做足够的工作,但我找不到更多的线索。
最终解决方案
在考虑了问题的正确答案(下面两个;一个简短,一个带代码)后,问题解决了;我将代码连同一些重要说明放在separate answer 中,以防止这个冗长的问题变得更长。
感谢大家的回答和cmets!
【问题讨论】:
我能问一下为什么您要实现自己的数据访问层,而不是使用或改进已经存在的众多开源解决方案之一吗? “ActiveRecord”部分是 SubSonic(它看起来很整洁,但如果您不使用 MSSQL,请不要打扰)。 “DataContract”部分是必要的,因为我们需要使用可以根据目的进行调整的业务对象(SubSonic 生成代码;生成的代码和自定义调整不混合)。两者之间的“翻译”是必要的邪恶。当然这一切都是题外话…… 您是否收到 InvalidOperationException 消息“从范围 '' 引用的 'ConsoleApplication1.Product2' 类型的变量 'w',但未定义”或其他? 这是我在查询提供程序是 LINQ to Objects 时得到的。当它是 LINQ to SQL 时,它是不同的(SubSonic 抛出 NotSupportedException),并且在完全错误的地方“检测到”错误。 SubSonic 源代码的相关部分是将w => 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 => w.Id == 0
确实在正文中使用了ParameterExpression
,我完全不认为它们可能需要是同一个实例(WTF?文档? ??)。我会调查并回复你。谢谢!
@diegov - +1 - 我认为这是错误的。然而,我要做的是在构造时将新的 ParameterExpression 输入到访问者中,并且从不在访问者内部构造它。每当它找到“旧”参数的实例时,就会用新的参数替换它。
@diegov - 确认:这就是问题所在!谢谢!出于好奇:你怎么知道它必须是同一个实例? :-)
我刚刚检查过,实际上主要是运气,问题在于编译表达式,而不仅仅是我错误回忆的构建它,而且我认为 subsonic 不会编译它。但它可能正在假设它是同一个实例,这可能是神秘异常的原因..
运气与否,这是一个非常敏锐的观察!你是对的,SubSonic 没有编译表达式——而是将它拆开来构建 SQL,这就是重点。【参考方案2】:
事实证明,棘手的部分只是新 lambda 表达式树中存在的 ParameterExpression
实例必须与在 IEnumerable<ParameterExpression>
参数中传递的 相同实例 Expression.Lambda
.
请注意,在TransformPredicateLambda
内部,我将t => 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 => w.Code == 1
,那么一切都可以正常工作,但如果我有w => w.Code == tmpVar
,其中tmpVar 的值为1,则它不起作用。是正确的还是只是我混淆了某些东西?附:无论如何,很棒的代码,谢谢分享!
@Lorenzo:.NET 4 类上有一个VisitMember。实际上,真正的最终解决方案比我在此处发布的更多,但它的时间更长且涉及更多。此版本不适用于捕获的变量。
@Lorenzo:我想那里叫VisitMemberAccess
。
是的,你是对的。我的意思是关于使它与捕获的变量一起工作的建议:)
@Lorenzo:你会想阅读this。【参考方案3】:
我尝试了简单(不完整)的实现来改变表达式p => p.Id == 15
(代码如下)。有一个名为“CrossMapping”的类定义了原始类型和“新”类型以及类型成员之间的映射。
每个表达式类型都有几个名为 Mutate_XY_Expression
的方法,这会产生新的变异表达式。方法输入需要原始表达式(MemberExpression originalExpression
)作为表达式模型,列表或参数表达式(IList<ParameterExpression> 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<T>()
方法。在执行此操作时,我发现如果原始表达式与此类似,例如x => 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<>
仅存在于 SubSonic 2 中,我正在使用 3。SS2 甚至没有 lambdas... 顺便说一句,您建议的解决方案需要 (a) 我的代码库是重新访问和一些(但不是全部;有时我们在我们需要的全部结果集上使用 LINQ to Objects)Func我认为如果您正确执行查询,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<DataContract.Widget>
实现。以上是关于改变谓词的表达式树以针对另一种类型的主要内容,如果未能解决你的问题,请参考以下文章