替换参数以指向 lambda 表达式中的嵌套参数
Replace parameter to point to nested parameter in lambda expression
由于 上的一些答案,我可以成功替换 lambda 表达式中的简单参数类型,但我不知道如何将参数从传入的 lambda 替换为嵌套参数。
考虑以下对象:
public class DtoColour {
public DtoColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoPerson
{
public DtoPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
FavouriteColours = new Collection<DtoFavouriteColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoFavouriteColour
{
public DtoColour Colour { get; set; }
public DtoPerson Person { get; set; }
}
public class DomainColour {
public DomainColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DomainPerson> People { get; set; }
}
public class DomainPerson {
public DomainPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
Colours = new Collection<DomainColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DomainColour> Colours { get; set; }
}
和存储库:
public class ColourRepository {
private IList<DtoColour> Colours { get; set; }
public ColourRepository()
{
var favColours = new Collection<DtoFavouriteColour>
{
new DtoFavouriteColour() { Person = new DtoPerson("Peter", "Parker") },
new DtoFavouriteColour() { Person = new DtoPerson("John", "Smith") },
new DtoFavouriteColour() { Person = new DtoPerson("Joe", "Blogs") }
};
Colours = new List<DtoColour>
{
new DtoColour("Red") { FavouriteColours = favColours },
new DtoColour("Blue"),
new DtoColour("Yellow")
};
}
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var coonvertedPred = MyExpressionVisitor.Convert(predicate);
return Colours.Where(coonvertedPred).Select(c => new DomainColour(c.Name)).ToList();
}
}
最后是一个表达式访问者,它将谓词转换为 Dto 模型的正确谓词
public class MyExpressionVisitor : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
public static Func<DtoColour, bool> Convert<T>(Expression<T> root)
{
var visitor = new MyExpressionVisitor();
var expression = (Expression<Func<DtoColour, bool>>)visitor.Visit(root);
return expression.Compile();
}
protected override Expression VisitParameter(ParameterExpression node)
{
var param = _parameters?.FirstOrDefault(p => p.Name == node.Name);
if (param != null)
{
return param;
}
if(node.Type == typeof(DomainColour))
{
return Expression.Parameter(typeof(DtoColour), node.Name);
}
if (node.Type == typeof(DomainPerson))
{
return Expression.Parameter(typeof(DtoFavouriteColour), node.Name);
}
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
protected override Expression VisitMember(MemberExpression node)
{
var exp = Visit(node.Expression);
if (node.Member.DeclaringType == typeof(DomainColour))
{
if (node.Type == typeof(ICollection<DomainPerson>))
{
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty("FavouriteColours"));
}
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty(node.Member.Name));
}
if (node.Member.DeclaringType == typeof(DomainPerson))
{
var nested = Expression.MakeMemberAccess(exp, typeof(DtoFavouriteColour).GetProperty("Person"));
return Expression.MakeMemberAccess(nested, typeof(DtoPerson).GetProperty(node.Member.Name));
}
return base.VisitMember(node);
}
}
目前我得到以下异常
[System.ArgumentException: Expression of type
'System.Collections.Generic.ICollection1[ExpressionVisitorTests.DtoFavouriteColour]'
cannot be used for parameter of type
'System.Collections.Generic.IEnumerable
1[ExpressionVisitorTests.DomainPerson]'
of method 'Boolean
Any[DomainPerson](System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson],
System.Func
2[ExpressionVisitorTests.DomainPerson,System.Boolean])']
这里有一个 dotnetfiddle 不起作用。
提前感谢您的帮助。
经过更多搜索后,我遇到了 this answer by John Skeet,这让我想出了一个可行的解决方案,其中包括为 ExpressionVisitor
上的 VisitMethodCall
方法添加覆盖以替换原始 MethodInfo
和一个新的用于正确的集合类型。
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments[0].Type == typeof(ICollection<DomainPerson>))
{
Expression obj = Visit(node.Object);
IEnumerable<Expression> args = Visit(node.Arguments);
if (obj != node.Object || args != node.Arguments)
{
var generic = typeof(Enumerable).GetMethods()
.Where(m => m.Name == node.Method.Name)
.Where(m => m.GetParameters().Length == node.Arguments.Count)
.Single();
var constructed = generic.MakeGenericMethod(typeof(DtoFavouriteColour));
return Expression.Call(obj, constructed, args);
}
}
return node;
}
我还需要确保我对 _parameters
集合的引用不会被对 VisitLambda<T>
的嵌套调用所取代,这可能在访问 node.Body
.
时发生
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = VisitAndConvert(node.Parameters, "VisitLambda");
// ensure parameters set but dont let original reference
// be overidden by nested calls
_parameters = parameters;
return Expression.Lambda(Visit(node.Body), parameters);
}
请参阅 dotnetfiddle 了解完整的解决方案。
如果谁有better/more优雅的解决方案请加答案让我标记
你已经解决了具体的问题,所以我不能说我要给你的建议是否 better/more 优雅,但肯定会更通用一些(删除了具体的 types/properties/assumptions),因此可以重复用于翻译来自不同模型类型的相似表达式。
代码如下:
public class ExpressionMap
{
private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public ExpressionMap Add<TFrom, TTo>()
{
typeMap.Add(typeof(TFrom), typeof(TTo));
return this;
}
public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
{
memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
return this;
}
public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);
private class MapVisitor : ExpressionVisitor
{
public ExpressionMap map;
private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node) => Map(node);
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
Expression mappedMember;
if (map.memberMap.TryGetValue(node.Member, out mappedMember))
return Visit(mappedMember);
return Expression.PropertyOrField(expression, node.Member.Name);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var arguments = Visit(node.Arguments);
var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
return Expression.Call(method, arguments);
}
return base.VisitMethodCall(node);
}
private Type Map(Type type)
{
Type mappedType;
return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
}
}
以及具体示例的用法:
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var map = new ExpressionMap()
.Add<DomainColour, DtoColour>()
.Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
.Add<DomainPerson, DtoPerson>();
var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}
如您所见,它允许您定义从一种类型到另一种类型的简单映射,并且可以选择从一种类型的成员到另一种类型的 member/expression(只要它们兼容)使用 "fluent" 语法与 lambda 表达式。没有指定映射的成员在原始代码中按名称映射。
定义映射后,实际处理当然是由自定义 ExpressionVisitor
完成的,与您的类似。不同之处在于它通过 type 映射和合并 ParameterExpression
s,并且还翻译每个 static 泛型方法 ,因此也应该与Queryable
和类似的。
由于
考虑以下对象:
public class DtoColour {
public DtoColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoPerson
{
public DtoPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
FavouriteColours = new Collection<DtoFavouriteColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoFavouriteColour
{
public DtoColour Colour { get; set; }
public DtoPerson Person { get; set; }
}
public class DomainColour {
public DomainColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DomainPerson> People { get; set; }
}
public class DomainPerson {
public DomainPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
Colours = new Collection<DomainColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DomainColour> Colours { get; set; }
}
和存储库:
public class ColourRepository {
private IList<DtoColour> Colours { get; set; }
public ColourRepository()
{
var favColours = new Collection<DtoFavouriteColour>
{
new DtoFavouriteColour() { Person = new DtoPerson("Peter", "Parker") },
new DtoFavouriteColour() { Person = new DtoPerson("John", "Smith") },
new DtoFavouriteColour() { Person = new DtoPerson("Joe", "Blogs") }
};
Colours = new List<DtoColour>
{
new DtoColour("Red") { FavouriteColours = favColours },
new DtoColour("Blue"),
new DtoColour("Yellow")
};
}
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var coonvertedPred = MyExpressionVisitor.Convert(predicate);
return Colours.Where(coonvertedPred).Select(c => new DomainColour(c.Name)).ToList();
}
}
最后是一个表达式访问者,它将谓词转换为 Dto 模型的正确谓词
public class MyExpressionVisitor : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
public static Func<DtoColour, bool> Convert<T>(Expression<T> root)
{
var visitor = new MyExpressionVisitor();
var expression = (Expression<Func<DtoColour, bool>>)visitor.Visit(root);
return expression.Compile();
}
protected override Expression VisitParameter(ParameterExpression node)
{
var param = _parameters?.FirstOrDefault(p => p.Name == node.Name);
if (param != null)
{
return param;
}
if(node.Type == typeof(DomainColour))
{
return Expression.Parameter(typeof(DtoColour), node.Name);
}
if (node.Type == typeof(DomainPerson))
{
return Expression.Parameter(typeof(DtoFavouriteColour), node.Name);
}
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
protected override Expression VisitMember(MemberExpression node)
{
var exp = Visit(node.Expression);
if (node.Member.DeclaringType == typeof(DomainColour))
{
if (node.Type == typeof(ICollection<DomainPerson>))
{
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty("FavouriteColours"));
}
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty(node.Member.Name));
}
if (node.Member.DeclaringType == typeof(DomainPerson))
{
var nested = Expression.MakeMemberAccess(exp, typeof(DtoFavouriteColour).GetProperty("Person"));
return Expression.MakeMemberAccess(nested, typeof(DtoPerson).GetProperty(node.Member.Name));
}
return base.VisitMember(node);
}
}
目前我得到以下异常
[System.ArgumentException: Expression of type 'System.Collections.Generic.ICollection
1[ExpressionVisitorTests.DtoFavouriteColour]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable
1[ExpressionVisitorTests.DomainPerson]' of method 'Boolean Any[DomainPerson](System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson], System.Func
2[ExpressionVisitorTests.DomainPerson,System.Boolean])']
这里有一个 dotnetfiddle 不起作用。
提前感谢您的帮助。
经过更多搜索后,我遇到了 this answer by John Skeet,这让我想出了一个可行的解决方案,其中包括为 ExpressionVisitor
上的 VisitMethodCall
方法添加覆盖以替换原始 MethodInfo
和一个新的用于正确的集合类型。
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments[0].Type == typeof(ICollection<DomainPerson>))
{
Expression obj = Visit(node.Object);
IEnumerable<Expression> args = Visit(node.Arguments);
if (obj != node.Object || args != node.Arguments)
{
var generic = typeof(Enumerable).GetMethods()
.Where(m => m.Name == node.Method.Name)
.Where(m => m.GetParameters().Length == node.Arguments.Count)
.Single();
var constructed = generic.MakeGenericMethod(typeof(DtoFavouriteColour));
return Expression.Call(obj, constructed, args);
}
}
return node;
}
我还需要确保我对 _parameters
集合的引用不会被对 VisitLambda<T>
的嵌套调用所取代,这可能在访问 node.Body
.
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = VisitAndConvert(node.Parameters, "VisitLambda");
// ensure parameters set but dont let original reference
// be overidden by nested calls
_parameters = parameters;
return Expression.Lambda(Visit(node.Body), parameters);
}
请参阅 dotnetfiddle 了解完整的解决方案。
如果谁有better/more优雅的解决方案请加答案让我标记
你已经解决了具体的问题,所以我不能说我要给你的建议是否 better/more 优雅,但肯定会更通用一些(删除了具体的 types/properties/assumptions),因此可以重复用于翻译来自不同模型类型的相似表达式。
代码如下:
public class ExpressionMap
{
private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public ExpressionMap Add<TFrom, TTo>()
{
typeMap.Add(typeof(TFrom), typeof(TTo));
return this;
}
public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
{
memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
return this;
}
public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);
private class MapVisitor : ExpressionVisitor
{
public ExpressionMap map;
private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node) => Map(node);
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
Expression mappedMember;
if (map.memberMap.TryGetValue(node.Member, out mappedMember))
return Visit(mappedMember);
return Expression.PropertyOrField(expression, node.Member.Name);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var arguments = Visit(node.Arguments);
var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
return Expression.Call(method, arguments);
}
return base.VisitMethodCall(node);
}
private Type Map(Type type)
{
Type mappedType;
return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
}
}
以及具体示例的用法:
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var map = new ExpressionMap()
.Add<DomainColour, DtoColour>()
.Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
.Add<DomainPerson, DtoPerson>();
var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}
如您所见,它允许您定义从一种类型到另一种类型的简单映射,并且可以选择从一种类型的成员到另一种类型的 member/expression(只要它们兼容)使用 "fluent" 语法与 lambda 表达式。没有指定映射的成员在原始代码中按名称映射。
定义映射后,实际处理当然是由自定义 ExpressionVisitor
完成的,与您的类似。不同之处在于它通过 type 映射和合并 ParameterExpression
s,并且还翻译每个 static 泛型方法 ,因此也应该与Queryable
和类似的。