我正在使用ExpressionVisitor将Expression<Func<T1,bool>>
的类型转换为Expression<Func<T2,bool>>
,并且效果非常好。但我注意到,如果表达式包含其他类型,因为导航属性ExpressionVisitor会导致异常..
假设我有两个不同的类。
Brand
- ID
- Name
- Models (navigation property)
Model
- ID
- BrandID
- Brand (navigation property)
- Name
这两个类完全相同,DTO以后缀“Info”命名。
所以我有四个班级Brand, BrandInfo, Model, ModelInfo
现在我有一个如下表达式。
Expression<Func<Brand,bool>> = x=> x.Name.Contains("Test")
此表达式可以Expression<Func<BrandInfo,bool>>
成功转换为ExpressionVisitor
。
但是,如果我想转换下面的表达式,它会生成异常。
Expression<Func<Brand,bool>> = x=> x.Models.Count(p=>p.Name.Contains("Model Test")) > 0)
因为ExpressionVisitor
仅适用于Brand
和BrandInfo
类,Brand
类包含IEnumerable<Model>
属性Models
,但目标类BrandInfo包含IEnumerable<ModelInfo>
属性为Models
。因此,当ExpressionVisitor
尝试绑定Models
属性时,它会抛出异常。
我找不到转换包含多种类型的表达式的方法。
这是我用来转换的代码
public static class MappingHelpers {
public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
this Expression<Func<TFrom, bool>> from) {
if(from == null) return null;
return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
}
public static Expression<Func<IQueryable<TTo>, IOrderedQueryable<TTo>>> Convert<TFrom, TTo>(
this Expression<Func<IQueryable<TFrom>, IOrderedQueryable<TFrom>>> from) {
if(from == null) return null;
return ConvertImpl<Func<IQueryable<TFrom>, IOrderedQueryable<TFrom>>, Func<IQueryable<TTo>, IOrderedQueryable<TTo>>>(from);
}
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
where TFrom : class
where TTo : class {
// figure out which types are different in the function-signature
var fromTypes = from.Type.GetGenericArguments();
var toTypes = typeof(TTo).GetGenericArguments();
if(fromTypes.Length != toTypes.Length)
throw new NotSupportedException(
"Incompatible lambda function-type signatures");
Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
for(int i = 0;i < fromTypes.Length;i++) {
if(fromTypes[i] != toTypes[i])
typeMap[fromTypes[i]] = toTypes[i];
}
// re-map all parameters that involve different types
Dictionary<Expression, Expression> parameterMap
= new Dictionary<Expression, Expression>();
ParameterExpression[] newParams = GenerateParameterMap<TFrom>(from, typeMap, parameterMap);
// rebuild the lambda
var body = new TypeConversionVisitor<TTo>(parameterMap).Visit(from.Body);
return Expression.Lambda<TTo>(body, newParams);
}
private static ParameterExpression[] GenerateParameterMap<TFrom>(Expression<TFrom> from,
Dictionary<Type, Type> typeMap,
Dictionary<Expression, Expression> parameterMap
) where TFrom : class {
ParameterExpression[] newParams =
new ParameterExpression[from.Parameters.Count];
for(int i = 0;i < newParams.Length;i++) {
Type newType;
if(typeMap.TryGetValue(from.Parameters[i].Type, out newType)) {
parameterMap[from.Parameters[i]] = newParams[i] =
Expression.Parameter(newType, from.Parameters[i].Name);
}
else {
newParams[i] = from.Parameters[i];
}
}
return newParams;
}
class TypeConversionVisitor<T> : ExpressionVisitor {
private readonly Dictionary<Expression, Expression> parameterMap;
public TypeConversionVisitor(
Dictionary<Expression, Expression> parameterMap) {
this.parameterMap = parameterMap;
}
protected override Expression VisitParameter(ParameterExpression node) {
// re-map the parameter
Expression found;
if(!parameterMap.TryGetValue(node, out found))
found = base.VisitParameter(node);
return found;
}
public override Expression Visit(Expression node) {
LambdaExpression lambda = node as LambdaExpression;
if(lambda != null && !parameterMap.ContainsKey(lambda.Parameters.First())) {
return new TypeConversionVisitor<T>(parameterMap).Visit(lambda.Body);
}
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node) {
// re-perform any member-binding
var expr = Visit(node.Expression);
if(expr.Type != node.Type) {
if(expr.Type.GetMember(node.Member.Name).Count() > 0) {
MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
.Single();
return Expression.MakeMemberAccess(expr, newMember);
}
else {
//This silly code had written to solve problems that differences between models caused.
return Expression.Equal(Expression.Constant(1, typeof(int)), Expression.Constant(1, typeof(int)));
}
}
return base.VisitMember(node);
}
}
public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
// build parameter map (from parameters of second to parameters of first)
var map = first.Parameters.Select((f, i) => new {
f,
s = second.Parameters[i]
}).ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with parameters from the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// apply composition of lambda expression bodies to parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
return first.Compose(second, Expression.AndAlso);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
return first.Compose(second, Expression.OrElse);
}
}
class ParameterRebinder : ExpressionVisitor {
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p) {
ParameterExpression replacement;
if(map.TryGetValue(p, out replacement)) {
p = replacement;
}
return base.VisitParameter(p);
}
}