在我目前正在处理的应用程序中,每个业务对象有两种:“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
实例,该实例具有描述MemberInfo
的{{1}}类型的属性。
表达式树及其参数集合(DataContract.Widget.Id
)中也有ParameterExpression
个实例,用于描述predicate.Parameters
;所有这些都会导致错误,因为可查询主体不包含该类型的小部件,而是DataContract.Widget
。
在搜索了一下后,我发现System.Linq.Expressions.ExpressionVisitor
(它的源代码可以在how-to的上下文中找到here),这提供了一种修改表达式树的便捷方法。在.NET 4中,这个类是开箱即用的。
有了这个,我实施了一个访客。这个简单的访问者只负责更改成员访问和参数表达式中的类型,但这足以使用谓词ActiveRecord.Widget
。
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
时,一切正常。因此,我假设我在访问者中没有做足够的工作,但我找不到任何更多的潜在客户。
考虑到问题的正确答案(下面两个;一个简短,一个带代码),问题解决了;我将代码与separate answer中的一些重要注释放在一起,以防止这个长问题变得更长。
感谢大家的回答和评论!
答案 0 :(得分:15)
看来你在这里的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声明的实例相同的实例(不仅仅是相同的类型和名称)。 我之前遇到过这种情况的问题,虽然我认为结果是我只是无法创建表达式,它只会引发异常。在任何情况下,您可能会尝试重用参数实例,看看它是否有帮助。
答案 1 :(得分:13)
事实证明,棘手的部分只是新lambda的表达式树中存在的ParameterExpression
实例必须与{{1>传递的相同的实例参数IEnumerable<ParameterExpression>
。
请注意,在Expression.Lambda
内,我将TransformPredicateLambda
作为“类型转换器”功能;那是因为在这种特定情况下,我们可以假设所有参数和成员访问都属于那一种特定类型。更高级的场景可能需要额外的逻辑。
代码:
t => typeof(TNewTarget)
答案 2 :(得分:6)
我尝试使用简单(不完整)实现来改变表达式p => p.Id == 15
(代码如下)。有一个名为“CrossMapping”的类,它定义了原始类型和“新”类型以及类型成员之间的映射。
对于每种表达式类型,都有几个名为Mutate_XY_Expression
的方法,这会产生新的变异表达式。方法输入需要原始表达式(MemberExpression originalExpression
)作为表达式模型,列表或参数表达式(IList<ParameterExpression> parameterExpressions
),它们是由“父”表达式定义的参数,应该由“父级”主体使用,以及定义类型和成员之间映射的映射对象(CrossMapping mapping
)。
对于完整实现,您可能需要父项表达式而不是参数的更多信息。但模式应该是相同的。
如你所知,Sample没有实现Visitor模式 - 这是因为简单。但转换成它们没有障碍。
我希望,这会有所帮助。
代码(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;
}
}
}
答案 3 :(得分:5)
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)));
}
}
答案 4 :(得分:-2)
ExecuteTypedList不能完成你想做的事吗? SubSonic将填充您的DTO / POCO。来自Rob Connery的博客:
ExecuteTypedList&LT;&GT;试图匹配 返回列的名称 的属性名称 传入式。在这个例子中他们 完全匹配 - 而事实并非如此 完全真实的世界。你可以得到 围绕这个通过别名列 - 以同样的方式为SQL命名 拨打:
return Northwind.DB.Select("ProductID as 'ID'", "ProductName as 'Name'", "UnitPrice as 'Price'")
.From<Northwind.Product>().ExecuteTypedList<Product>();
的链接
答案 5 :(得分:-4)
我认为如果正确地进行查询,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);
}