问题
I am trying to replace the parameter type in a lambda expression from one type to another.
I have found other answers on stackoverflow i.e. this one but I have had no luck with them.
Imagine for a second you have a domain object and a repository from which you can retrieve the domain object.
however the repository has to deal with its own Data transfer objects and then map and return domain objects:
ColourDto.cs
public class DtoColour {
public DtoColour(string name)
{
Name = name;
}
public string Name { get; set; }
}
DomainColour.cs
public class DomainColour {
public DomainColour(string name)
{
Name = name;
}
public string Name { get; set; }
}
Repository.cs
public class ColourRepository {
...
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
// Context.Colours is of type ColourDto
return Context.Colours.Where(predicate).Map().ToList();
}
}
As you can see this will not work as the predicate is for the domain model and the Collection inside the repository is a collection of Data transfer objects.
I have tried to use an ExpressionVisitor
to do this but cannot figure out how to just change the type of the ParameterExpression
without an exception being thrown for example:
Test scenario
public class ColourRepository {
...
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var visitor = new MyExpressionVisitor();
var newPredicate = visitor.Visit(predicate) as Expression<Func<ColourDto, bool>>;
return Context.Colours.Where(newPredicate.Complie()).Map().ToList();
}
}
public class MyExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
return Expression.Parameter(typeof(ColourDto), node.Name);
}
}
finally here is the exception:
System.ArgumentException : Property 'System.String Name' is not defined for type 'ColourDto'
Hope someone can help.
EDIT: Here is a dotnetfiddle
still doesnt work.
Edit: Here is a working dotnetfiddle
Thanks Eli Arbel
回答1:
You need to do a few things for this to work:
- Replace parameter instance both at the
Expression.Lambda
and anywhere they appear in the body - and use the same instance for both. - Change the lambda's delegate type.
- Replace the property expressions.
Here's the code, with added generics:
public static Func<TTarget, bool> Convert<TSource, TTarget>(Expression<Func<TSource, bool>> root)
{
var visitor = new ParameterTypeVisitor<TSource, TTarget>();
var expression = (Expression<Func<TTarget, bool>>)visitor.Visit(root);
return expression.Compile();
}
public class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameters?.FirstOrDefault(p => p.Name == node.Name) ??
(node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : 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)
{
if (node.Member.DeclaringType == typeof(TSource))
{
return Expression.Property(Visit(node.Expression), node.Member.Name);
}
return base.VisitMember(node);
}
}
回答2:
Properties are defined separately for each type.
That error happens because you can't get the value of a property defined by DomainColour
from a value of type ColourDto
.
You need to visit every MemberExpression
that uses the parameter and return a new MemberExpression
that uses that property from the new type.
来源:https://stackoverflow.com/questions/38316519/replace-parameter-type-in-lambda-expression