将谓词的表达式树突变为目标另一种类型
介绍
在我当前正在处理的应用程序中,每种业务对象都有两种:“ ActiveRecord”类型和“ DataContract”类型。因此,例如,将有:
namespace ActiveRecord {
class Widget {
public int Id { get; set; }
}
}
namespace DataContract {
class Widget {
public int Id { get; set; }
}
}
数据库访问层负责在族之间进行转换:您可以告诉它更新a
DataContract.Widget
,它将神奇地创建一个ActiveRecord.Widget
具有相同属性值的an 并将其保存。
尝试重构此数据库访问层时出现了问题。
问题
我想将以下方法添加到数据库访问层:
// 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
为describe
DataContract.Widget.Id
。ParameterExpression
在表达式树及其参数集合(predicate.Parameters
)中也有实例描述DataContract.Widget
;
所有这些都会导致错误,因为可查询主体不包含该类型的小部件,而是ActiveRecord.Widget
。
搜索了一下后,我发现System.Linq.Expressions.ExpressionVisitor
(其来源可以发现这里的如何做的情况下),它提供了一个方便的方式来修改表达式树。在.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
,一切正常。因此,我假设我没有在访问者中做足够的工作,但是我找不到更多的线索可循。
最终解决方案
在考虑了问题的正确答案之后(以下两个);一个简短的问题,一个带有代码的问题得以解决;我将代码和一些重要说明放在一个单独的答案中,以防止这个漫长的问题变得更长。
感谢大家的回答和评论!
-
似乎您在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声明的实例具有相同的实例(不仅仅是相同的类型和名称)。在这种情况下,我曾经遇到过问题,尽管我认为结果是我无法创建表达式,但它只会引发异常。无论如何,您都可以尝试重用参数实例,以查看是否有帮助。
-
将BufferedImage转换为另一种类型
2021-01-30 关注 0 浏览153 1答案
-
Java:如何将变量从一种类型动态转换为另一种类型?
2021-02-02 关注 0 浏览110 1答案
-
SQL中用于将某种数据类型的表达式显式转换为另一种数据类型的函数是
2022-03-03 关注 0 浏览63 1答案
-
如何将一种数字类型的切片转换为另一种类型
2021-02-01 关注 0 浏览111 1答案
-
为什么我不能在Go中用一种类型的切片替代另一种类型?
2021-02-01 关注 0 浏览81 1答案
-
将数组中的多个字节转换为Go中的另一种类型
2021-02-01 关注 0 浏览117 1答案
-
振荡分为两种类型:一种是______,另一种是______
2022-05-22 关注 0 浏览18 1答案
-
如何检测类型是否为另一种通用类型
2021-02-02 关注 0 浏览101 1答案
-
如何将一种数据类型的所有Sql列更改为另一种
2021-05-10 关注 0 浏览109 1答案
-
为什么即使只有一种可能的返回类型,方法调用表达式也具有动态类型?
2021-02-02 关注 0 浏览72 1答案