C# 将谓词的表达式树变异为另一种类型 简介
在我目前正在开发的应用程序中,每个业务对象有两种类型:“ActiveRecord”类和“DataContract”类。例如,将有:C# 将谓词的表达式树变异为另一种类型 简介,c#,linq,lambda,expression-trees,C#,Linq,Lambda,Expression Trees,在我目前正在开发的应用程序中,每个业务对象有两种类型:“ActiveRecord”类和“DataContract”类。例如,将有: namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } } 数据库访问层
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);
}
这将不起作用,因为在典型场景中,例如:
predicate == w => w.Id == 0;
…表达式树包含一个MemberAccessExpression
实例,该实例具有类型为MemberInfo
的属性,该属性描述DataContract.Widget.Id
。
表达式树及其参数集合(predicate.Parameters
)中也有描述DataContract.Widget
)的ParameterExpression
实例;所有这些都将导致错误,因为可查询的主体不包含该类型的小部件,而是ActiveRecord.widget
在搜索了一点之后,我找到了(它的源代码可以在how-to的上下文中找到),它提供了一种修改表达式树的方便方法。在.NET4中,这个类是开箱即用的
有了这个,我实现了一个访问者。这个简单的访问者只负责更改成员访问和参数表达式中的类型,但这就足够使用谓词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);
}
}
结果
好消息是lambda
构造得很好。坏消息是它不起作用;当我尝试使用它的时候,它突然向我袭来,异常消息真的毫无帮助
我已经检查了我的代码生成的lambda和具有相同表达式的硬编码lambda;它们看起来一模一样。我花了几个小时在调试程序中试图找到一些差异,但我找不到
当谓词为w=>w.Id==0
时,lambda
看起来就像referenceLambda
。但是后者可以与例如IQueryable.Where
一起使用,而前者不能;我已经在调试器的即时窗口中尝试过了
我还应该提到,当谓词为w=>true
时,一切正常。因此,我假设我在访问者中做的工作不够,但是我找不到更多的线索可以遵循
最终解决方案
在考虑到问题的正确答案后(下面有两个;一个简短,一个带有代码),问题得到了解决;我把代码和一些重要的注释放在一个文件夹中,以避免这个长问题变得更长
谢谢大家的回答和评论 我认为,如果正确执行查询,linqtosql将产生理想的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);
}
IEnumerable GetMany(
Func谓词)
{
//获取小部件
IQueryable qry=dc.Widgets.Select(w=>TODO:CONVERT_TO_DataContract.Widget);
返回qry.Where(谓词);
}
ExecuteTypedList不能完成您想要做的事情吗?亚音速将填充DTO/POCO。来自Rob Connery的博客:
ExecuteTypedList尝试匹配
返回到的列的名称
属性的名称
传入类型。在这个例子中,他们
完全匹配-这不是
完全真实的世界。你可以
通过对列进行别名处理来解决此问题-
与使用SQL别名的方式相同
电话:
返回Northwind.DB.Select(“ProductID为'ID'”,“ProductName为'Name'”,“单价为'Price'”)
.From().ExecuteTypedList();
以下是Rob的链接似乎您在VisitMember()中生成了两次参数表达式: …因为base.Visit()将在我想象的VisitParameter和GetMany()中结束:
var lambda=Expression.lambda(
visitor.Visit(谓词.Body),
predicate.Parameters.Select(p=>visitor.Visit(p));
如果在主体中使用ParameterExpression,则它必须与为Lambda声明的实例相同(不仅仅是相同的类型和名称)。
我以前在这种情况下遇到过问题,尽管我认为结果是我无法创建表达式,但它只会引发一个异常。在任何情况下,您都可以尝试重用参数实例,看看它是否有帮助。我尝试了简单的(不完整)用于变异表达式p=>p.Id==15
(代码如下)的实现。有一个名为“CrossMapping”的类定义了原始类型和“新”类型以及类型成员之间的映射
每个表达式类型都有几个名为Mutate\u XY\u Expression
的方法,这些方法生成新的变异表达式。方法输入需要原始表达式(MemberExpression originalExpression
)作为表达式模型、列表或参数表达式(IList parameterExpressions
)它们是由“parent”表达式定义的参数,应该由“parent”主体使用,以及定义类型和成员之间映射的映射对象(CrossMapping-mapping
)
对于完整的实现,您可能需要更多来自父表达式的信息,而不是参数。但是模式应该是相同的
正如您所知,Sample并没有实现访问者模式——这是因为简单。但转换为访问者模式并没有任何障碍
我希望这会有帮助
代码(C#4.0):
使用系统;
使用System.Collections.Generic;
使用System.Linq;
运用系统反思;
使用System.Linq.Expressions;
名称
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.
}
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);
}
return Northwind.DB.Select("ProductID as 'ID'", "ProductName as 'Name'", "UnitPrice as 'Price'")
.From<Northwind.Product>().ExecuteTypedList<Product>();
var converted = Expression.MakeMemberAccess(
base.Visit(node.Expression),
activeRecordType.GetProperty(node.Member.Name));
var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>(
visitor.Visit(predicate.Body),
predicate.Parameters.Select(p => visitor.Visit(p));
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;
}
}
}
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();
}
}
}
x => x.SubObjects
.AsQueryable()
.SelectMany(y => y.GrandChildObjects)
.Any(z => z.Value == 3)
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)));
}
}