Заменить тип параметра в лямбда-выражении
Я пытаюсь заменить тип параметра в лямбда-выражении от одного типа к другому.
Я нашел другие ответы на stackru, т.е. этот, но мне не повезло с ними.
Представьте на секунду, что у вас есть объект домена и хранилище, из которого вы можете получить объект домена.
однако хранилище должно иметь дело с собственными объектами передачи данных, а затем отображать и возвращать объекты домена:
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();
}
}
Как вы можете видеть, это не сработает, так как предикат предназначен для модели предметной области, а коллекция внутри репозитория является коллекцией объектов передачи данных.
Я пытался использовать ExpressionVisitor
чтобы сделать это, но не могу понять, как просто изменить тип ParameterExpression
например, без исключения
Тестовый сценарий
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);
}
}
наконец, вот исключение:
System.ArgumentException: свойство 'System.String Name' не определено для типа 'ColourDto'
Надеюсь, кто-то может помочь.
РЕДАКТИРОВАТЬ: Вот это dotnetfiddle
все еще не работает.
Редактировать: вот рабочий dotnetfiddle
Спасибо Eli Arbel
3 ответа
Вам нужно сделать несколько вещей для этого:
- Замените экземпляр параметра как на
Expression.Lambda
и везде, где они появляются в теле - и используют один и тот же экземпляр для обоих. - Измените тип делегата лямбды.
- Замените выражения свойств.
Вот код с добавленными обобщениями:
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);
}
}
Свойства определяются отдельно для каждого типа.
Эта ошибка происходит, потому что вы не можете получить значение свойства, определенного DomainColour
от значения типа ColourDto
,
Вам нужно посетить каждый MemberExpression
который использует параметр и возвращает новый MemberExpression
который использует это свойство из нового типа.
Ответ Эли великолепен.
Но в моем случае у меня есть лямбда, внутри которой есть еще одна лямбда. Таким образом, он устанавливает «_parameter» дважды, переопределяя старый.
бывший:
Expression<Func<IObjectWithCompanyUnits, bool>> expr = e => e.CompanyUnits.Any(cu => cu.ID == GetCurrentCompanyUnitId());
Посетитель нарушает приведенное выше выражение.
Поэтому я изменил исходный ответ на свой случай:
public class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
private Dictionary<int, ReadOnlyCollection<ParameterExpression>> _parameters = new();
private int currentLambdaIndex = -1;
protected override Expression VisitParameter(ParameterExpression node)
{
var prms = _parameters.Count > currentLambdaIndex ? _parameters[currentLambdaIndex] : null;
var p = prms?.FirstOrDefault(p => p.Name == node.Name);
if (p != null)
{
return p;
}
else
{
if (node.Type == typeof(TSource))
{
return Expression.Parameter(typeof(TTarget), node.Name);
}
else
{
return node;
}
}
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
currentLambdaIndex++;
try
{
_parameters[currentLambdaIndex] = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters[currentLambdaIndex]);
}
finally
{
currentLambdaIndex--;
}
}
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);
}
}