I use Automapper and three-tier architecture in my application. I have a method that accepts a Linq (LambdaExpression) request as a parameter. To pass this parameter to another layer, I need to reassign it. So, I created an ExpressionExtension class that is responsible for returning a reassigned expression:
public static class ExpressionExtensions
{
public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(
this Expression<Func<TSource, TResult>> expression)
{
Contract.Requires(expression != null);
Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);
var newParameter = Expression.Parameter(typeof(TDestination));
Contract.Assume(newParameter != null);
var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
{
throw new InvalidOperationException("Unable to remap expression");
}
return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
}
}
And the code for AutoMapVisitor:
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly ParameterExpression _newParameter;
private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
public AutoMapVisitor(ParameterExpression newParameter)
{
_newParameter = newParameter;
}
protected override Expression VisitMember(MemberExpression node)
{
var propertyMaps = _typeMap.GetPropertyMaps();
var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
if (propertyMap == null)
{
return base.VisitMember(node);
}
var destinationProperty = propertyMap.DestinationProperty;
var destinationMember = destinationProperty.MemberInfo;
var property = destinationMember as PropertyInfo;
if (property == null)
{
return base.VisitMember(node);
}
var newPropertyAccess = Expression.Property(_newParameter, property);
return base.VisitMember(newPropertyAccess);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
var source = node.Parameters.FirstOrDefault();
TypeMap typeMap = source == null
? null
: Mapper.GetAllTypeMaps().FirstOrDefault(tMap => tMap.SourceType == source.Type);
if (typeMap!=null)
{
var ts = typeMap.SourceType;
var td = typeMap.DestinationType;
var res = node.ReturnType;
return VisitLambda(node, ts, td, res);
}
return base.VisitLambda<T>(node);
}
private Expression<Func<TOut,TResult>> VisitLambda<T,TIn, TOut, TResult>(Expression<T> node, TIn source, TOut destination, TResult result)
{
ParameterExpression pe = Expression.Parameter(typeof (TIn), "innerParam");
var value = typeof(T) as Func<TIn, TResult>;
Expression<Func<TIn, TResult>> input = Expression.Lambda<Func<TIn, TResult>>(Expression.Invoke(node.Body,node.Parameters));
return input.RemapForType<TIn, TOut, TResult>();
}
}
I understand that I need to override the base Lambda Visitor and call the ExpressionExtension.RemapForType method again to reassign the internal lambda. But how can I pass the correct parameters to it? So, I decided to create a general method, but nothing works correctly.
Could you help me find the right solution? For example, something like this:
Expression<Func<TSource,TResult>> a = val => val.FullName == "ABC" && val.SomeEnumerableProperty.Any(inner => inner.City == "Moscow");
Expression<Func<TDestination, TResult>> b = a.RemapForType<TSource,TDestination,TResult>();
, , . , , .
, :
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly TypeMap _typeMap;
private readonly List<TypeMap> _typeMaps = new List<TypeMap>();
private readonly Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public AutoMapVisitor(ParameterExpression newParameter)
{
_typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
Contract.Assume(_typeMap != null);
_typeMaps.Add(_typeMap);
_typeMaps.AddRange(FillNestedTypeMaps(_typeMap));
parameterMap.Add(newParameter.Type, newParameter);
foreach (TypeMap map in _typeMaps)
{
if (parameterMap.ContainsKey(map.DestinationType)) continue;
parameterMap.Add(map.DestinationType, Expression.Parameter(map.DestinationType, map.DestinationType.Name.ToLower()));
}
}
private IEnumerable<TypeMap> FillNestedTypeMaps(TypeMap tMap)
{
List<TypeMap> result = new List<TypeMap>();
IEnumerable<PropertyMap> pMaps = tMap.GetPropertyMaps();
var list = Mapper.GetAllTypeMaps()
.Where(
map => map.SourceType.IsClass && map.DestinationType.IsClass)
.Where(map =>
!(map.Equals(tMap) ||
(map.SourceType == tMap.DestinationType && map.DestinationType == tMap.SourceType)))
.Where(
map =>
pMaps
.Any(
pi =>
{
var pis = pi.SourceMember as PropertyInfo;
if (pis == null) return false;
bool forSource = pis.PropertyType == map.SourceType ||
(pis.PropertyType.IsGenericType &&
pis.PropertyType.GetGenericArguments()[0] == map.SourceType);
bool forDestination = pi.DestinationPropertyType == map.DestinationType ||
(pi.DestinationPropertyType.IsGenericType &&
pi.DestinationPropertyType.GetGenericArguments()[0] == map.DestinationType);
return forSource && forDestination;
}))
.ToList();
if (list.Count > 0)
{
result.AddRange(list);
foreach (TypeMap typeMap in list)
{
result.AddRange(FillNestedTypeMaps(typeMap));
}
}
return result;
}
private Type Map(Type type)
{
var tMap = _typeMaps.FirstOrDefault(map => map.SourceType == type);
Contract.Assume(tMap != null);
return tMap.DestinationType;
}
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;
}
private Expression Map(MemberInfo mi, Expression exp)
{
Expression val;
if (!memberMap.TryGetValue(mi, out val))
{
foreach (PropertyMap propertyMap in
_typeMaps.Select(map => map.GetPropertyMaps().SingleOrDefault(m => m.SourceMember == mi))
.Where(propertyMap => propertyMap != null))
{
memberMap.Add(mi, val = Expression.PropertyOrField(exp, propertyMap.DestinationProperty.MemberInfo.Name));
break;
}
}
return val;
}
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
return Map(node.Member, expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node)
{
return Map(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
var args = Visit(node.Arguments);
var genericArgs = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
return Expression.Call(method, args);
}
return base.VisitMethodCall(node);
}
}
:
variable 'person' of type 'Reestr.DAL.Entities.Person' referenced from scope '', but it is not defined
, , (, Any). , , ! , Enumerable , , . - , , ! !
@MBoros, Automapper (, ). ServiceStack.OrmLite !!! , , ...