samedi 14 octobre 2017

Expression and Automapper

Somewhere in the internet I found below class which I am using to convert Expression<Func<T, bool>> from DTO to Domain:

public class EvaluateVariableVisitor<TEntity, TDto> : ExpressionVisitor
{
    private readonly ParameterExpression _dtoParameter;
    private readonly ParameterExpression _entityParameter;
    private readonly IMapper _mapper;

    public EvaluateVariableVisitor()
    {
        _entityParameter = Expression.Parameter(typeof(TEntity));
        _dtoParameter = Expression.Parameter(typeof(TDto));
        _mapper = AutoMapperConfig.Initialize();
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        try
        {
            //change dto to entity type.
            if (node.Expression.Type == _dtoParameter.Type)
            {
                var reducedExpression = Visit(node.Expression);

                //TypeMap typeMap = Mapper.Configuration.FindTypeMapFor<TDto, TEntity>();
                TypeMap typeMap = _mapper.ConfigurationProvider.FindTypeMapFor<TDto, TEntity>();

                //find the correct name of the property in the destination object by using the name of the source objekt
                string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
                                                        .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
                                                        .Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();


                //find the correct name of the property in the destination object by using the name of the source objekt
                //string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
                //                                        .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
                //                                        .Select(propertyMap => propertyMap.DestinationProperty.Name).Single();

                var newMember = _entityParameter.Type.GetMember(destinationPropertyName).First();

                return Expression.MakeMemberAccess(reducedExpression, newMember);
            }

            //Recurse down to see if we can simplify...
            var expression = Visit(node.Expression);

            //If we've ended up with a constant, and it's a property or a field,
            //we can simplify ourselves to a constant
            var constantExpression = expression as ConstantExpression;
            if (constantExpression != null)
            {
                object container = constantExpression.Value;
                object value = null;

                var memberAsFieldInfo = node.Member as FieldInfo;
                var memberAsPropertyInfo = node.Member as PropertyInfo;

                if (memberAsFieldInfo != null)
                {
                    value = memberAsFieldInfo.GetValue(container);
                }

                if (memberAsPropertyInfo != null)
                {
                    value = memberAsPropertyInfo.GetValue(container, null);
                }

                if (value != null)
                {
                    return Expression.Constant(value);
                }
            }

            return base.VisitMember(node);
        }
        catch (System.Exception exc)
        {
            var ex = exc.Message;
            throw;
        }
    }

    //change type from dto to entity --> otherwise the generated expression tree would throw an exception, because of missmatching types(Dto can't be used in Entity expression).
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node.Type == _dtoParameter.Type ? _entityParameter : node;
    }
}

It was working great but I faced an issue when I had property of the same type inside my DTO:

  public class InventoryApplicationDto
  {
      public int Id { get; set; }
      public string Name{ get; set; }
      public InventoryApplicationDto ParentApplication { get; set; }    
  }

First of all I had to change:

string destinationPropertyName = typeMap.GetPropertyMaps() 
    .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
    .Select(propertyMap => propertyMap.DestinationProperty.Name).Single();

To:

string destinationPropertyName = typeMap.GetPropertyMaps() 
    .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
    .Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();

My problem is that I am getting error:

System.ArgumentException: Property 'Int32 ID' is not defined for type 'InventoryApplicationDto'

on line:

return Expression.MakeMemberAccess(reducedExpression, newMember);

Is MakeMemberAccess method case sensitive or there is another issue?





Aucun commentaire:

Enregistrer un commentaire