inspired by this answer I\'m trying to map a property on a model class to an expression based on the actual entity. These are the two classes involved:
publi
Another solution would be to use AutoMapper to map complex types and modify the resulting expression query with an ExpressionTransformer before it gets executed. I will try to explain with a complete sample:
Model classes
Some POCO classes just for holding data.
public enum CostUnitType
{
None = 0,
StockUnit = 1,
MachineUnit = 2,
MaintenanceUnit = 3
}
public class CostUnit
{
public string CostUnitId { get; set; }
public string WorkplaceId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public CostUnitType Type { get; set; }
public override string ToString()
{
return CostUnitId + " " + Name + " " + Type;
}
}
public class StockUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MachineUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MaintenanceUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
The ExpressionTransformer class
Most of the time, using the Mapper static class is fine, but sometimes you need to map the same types with different configurations, so you need to explicitly use an IMappingEngine like this:
var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var engine = new MappingEngine(configuration);
The way to create a MappingEngine is not obvious at all. I had to dig in the source code to find how it was done.
public static class ExpressionTransformer
{
private static readonly MappingEngine Mapper;
///
/// Initializes the class.
/// Creates an instance of AutoMapper. Initializes mappings.
///
static ExpressionTransformer()
{
ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
// Create mapping
// Maps Id from StockUnit to CostUnitId from CostUnit
configurationStore.CreateMap()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MachineUnit to CostUnitId from CostUnit
configurationStore.CreateMap()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MaintenanceUnit to CostUnitId from CostUnit
configurationStore.CreateMap()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Create instance of AutoMapper
Mapper = new MappingEngine(configurationStore);
}
public static Expression> Tranform(Expression> sourceExpression)
{
// Resolve mappings by AutoMapper for given types.
var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination));
if (map == null)
{
throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name));
}
// Transform from TSource to TDestination with specified mappings
var visitor = new ParameterTypeVisitor(sourceExpression, map.GetPropertyMaps());
var expression = visitor.Transform();
return expression;
}
private class ParameterTypeVisitor : ExpressionVisitor
{
private readonly Dictionary _parameters;
private readonly Expression> _expression;
private readonly IEnumerable _maps;
public ParameterTypeVisitor(Expression> expression, IEnumerable maps)
{
_parameters = expression.Parameters
.ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name));
_expression = expression;
_maps = maps;
}
public Expression> Transform()
{
return (Expression>) Visit(_expression);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(TSource))
{
var memberName = node.Member.Name;
var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type
&& p.SourceMember.Name == memberName);
if (member != null)
{
// Return Property from TDestination
var expression = Visit(node.Expression);
return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo);
}
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
var parameter = _parameters[node.Name];
return parameter;
}
protected override Expression VisitLambda(Expression node)
{
var expression = Visit(node.Body);
return Expression.Lambda(expression, _parameters.Select(x => x.Value));
}
}
}
Usage
To Convert an expression we just need to call Transform Method of ExpressionTransformer class
Expression> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC";
// Call Transform method.
Expression> costUnitQuery = ExpressionTransformer.Tranform(stockQuery);
Result