10
\$\begingroup\$

Using Entity Framework we have a number of entities, which we want to hide from the rest of the calling code to remove the direct dependency on the database. We're doing so by using DTOs, which for the most part are identical to the entities. We also have an abstract generic provider and persister pattern which handle the conversion between the dtos and the entities, and all of the database interaction. The general loading method of the base abstract provider uses the following signature:

protected virtual IEnumerable<TDto> Load(Expression<Func<TEntity, bool>> filter)

So any provider that extends the abstract can look like this:

public IEnumerable<FooDto> GetSomeFilteredData(IEnumerable<string> identifiers)
{
  return base.Load(entity => identifiers.Contains(entity.SomeProperty));
}

Therefore hiding the entities from the calling code.

This pattern works well for us, but I have been investigating how to manipulate expressions so that I could put the following in the base provider:

public virtual IEnumerable<TDto> Load(Expression<Func<TDto, bool>> filter)

The base provider would then convert the Expression<Func<TDto, bool>> into the equivalent entity expression, and load the data as before, therefore removing the need to write tedious methods and give us more control in the calling code.

The end result has been the following ExpressionConverter

public class ExpressionConverter<Dto, Entity>
  where Dto : class, new() where Entity : class, new()
{
  private MappedConverter<Dto, Entity> converter;

  public ExpressionConverter(MappedConverter<Dto, Entity> converter)
  {
    this.converter = converter;
  }

  public Expression<Func<Entity, bool>> ConvertExpr(Expression<Func<Dto, bool>> expr)
  {
    ParameterExpression entityParam = Expression.Parameter(typeof(Entity), "e");
    Expression entityExpression = this.ConvertExpression(expr.Body, entityParam);

    var result = Expression.Lambda<Func<Entity, bool>>(entityExpression, entityParam);
    return result;
  }

  private Expression ConvertExpression(Expression expression, ParameterExpression entityParam)
  {
    if (expression is BinaryExpression binary)
    {
      return this.ConvertBinaryExpression(binary, entityParam);
    }
    if (expression is MemberExpression member)
    {
      return this.ConvertMemberExpression(member, entityParam);
    }
    if (expression is MethodCallExpression method)
    {
      return this.ConvertMethodCallExpression(method, entityParam);
    }
    return expression;
  }

  private Expression ConvertBinaryExpression(BinaryExpression binary, ParameterExpression param)
  {
    Expression left = this.ConvertExpression(binary.Left, param);
    Expression right = this.ConvertExpression(binary.Right, param);
    ExpressionType nodeType = binary.NodeType;
      return this.CombineExpressions(left, right, nodeType);
  }

  private Expression ConvertMemberExpression(Expression expression, ParameterExpression entityParam)
  {
    if (this.IsParseableMemberExpression(expression))
    {
      MemberExpression memberExpr = expression as MemberExpression;
      var value = Expression.Lambda(memberExpr).Compile().DynamicInvoke();
      return Expression.Constant(value);
    }
    else if (this.IsConvertibleMember(expression))
    {
      MemberInfo dtoMember = (expression as MemberExpression).Member;
      Mapping<Dto, Entity> mapping = this.converter.GetMappingFromMemberName<Dto>(dtoMember.Name);
      MemberExpression entityMemberExpr = Expression.MakeMemberAccess(entityParam, mapping.EntityProperty);
      return entityMemberExpr;
    }
    return expression;
  }

  private Expression ConvertMethodCallExpression(MethodCallExpression expression, ParameterExpression entityParam)
  {
    Expression objectExpr = this.ConvertMemberExpression(expression.Object, entityParam);
    IEnumerable<Expression> argumentExpressions = expression.Arguments.Select(x => this.ConvertMemberExpression(x, entityParam));
    MethodInfo method = expression.Method;

    Expression result = Expression.Call(objectExpr, method, argumentExpressions);
    return result;
  }

  private bool IsConvertibleMember(Expression expression)
  {
    return expression.NodeType == ExpressionType.MemberAccess;
  }

  private bool IsParseableMemberExpression(Expression expression)
  {
    if (expression is MemberExpression memberExpression)
    {
      return Regex.IsMatch(expression.ToString(), @"value[(].*[)]")
        || this.IsDateTimeExpression(memberExpression);
    }
    return false;
  }

  private bool IsDateTimeExpression(MemberExpression expression)
  {
    return Regex.IsMatch(expression.ToString(), @"DateTime\.(Today|Now)");
  }

  private Expression CombineExpressions(Expression left, Expression right, ExpressionType type)
  {
    switch (type)
    {
      case ExpressionType.And:                return Expression.And(left, right);
      case ExpressionType.AndAlso:            return Expression.AndAlso(left, right);
      case ExpressionType.Equal:              return Expression.Equal(left, right);
      case ExpressionType.ExclusiveOr:        return Expression.ExclusiveOr(left, right);
      case ExpressionType.GreaterThan:        return Expression.GreaterThan(left, right);
      case ExpressionType.GreaterThanOrEqual: return Expression.GreaterThanOrEqual(left, right);
      case ExpressionType.LessThan:           return Expression.LessThan(left, right);
      case ExpressionType.LessThanOrEqual:    return Expression.LessThanOrEqual(left, right);
      case ExpressionType.NotEqual:           return Expression.NotEqual(left, right);
      case ExpressionType.Or:                 return Expression.Or(left, right);
      case ExpressionType.OrElse:             return Expression.OrElse(left, right);
      default:
        throw new Exception($"Unsupported expression type: {type.ToString()}");
    }
  }
}

Where MappedConverter<Dto, Entity> is the following:

public class MappedConverter<Dto, Entity> where Dto : class, new() where Entity : class, new()
{
  public List<Mapping<Dto, Entity>> Mappings { get; set; }

  public MappedConverter(params Mapping<Dto, Entity>[] maps)
  {
    this.Mappings = maps.ToList();
  }

  public Mapping<Dto, Entity> GetMappingFromMemberName<T>(string name)
  {
    if (typeof(T) == typeof(Dto))
    {
      return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name);
    }
    else if (typeof(T) == typeof(Entity))
    {
      return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name);
    }
    throw new Exception($"Cannot get mapping for {typeof(T).Name} from MappedConverter<{typeof(Dto).Name}, {typeof(Entity).Name}>");
  }
}

And Mapping<Dto, Entity> is the following:

public class Mapping<Dto, Entity>
{
  public PropertyInfo DtoProperty { get; }
  public PropertyInfo EntityProperty { get; }
}

Notes:

The conversion only has to handle Expressions of the form Expression<Func<Dto, bool>>

I am specifically looking for ways to improve the IsParseableMemberExpression and IsDateTimeExpression methods

IsParseableMemberExpression uses Regex to see if the expression is representing a variable in the full expression. e.g. variable in x => x.Value == variable

\$\endgroup\$
  • 1
    \$\begingroup\$ I agree that having to reference TEntity is compromising your otherwise quite extensive separation of layers. However, the issue in only using TDto is that there is no guarantee that its related TEntity has a similar property with which the expression would work. I'm not saying it can't technically be done, but is the effort required worth the payoff? It's counterintuitive to want direct control over the entity properties you're forcibly trying to abstract. You're essentially asking for zero type safety for your filters, which seems like it can become a source of future issues. \$\endgroup\$ – Flater May 2 '18 at 11:26
  • \$\begingroup\$ @Flater I understand the concern about type safety but that is something we manage at the data layer when creating the mapped converters. In our experience the effort creating this has already been worth the payoff because of the volume of new DTOs and Entities we are having to add, which are largely simple objects parsed from csv files, and any querying is relatively straightforward. For larger or more complex objects we are still using the old pattern to help with safety \$\endgroup\$ – JChristen May 2 '18 at 12:43
  • 1
    \$\begingroup\$ That makes sense now that you're building the intermediate layer. But suppose you're done with that, and a different developer is going to write the layer that consumes your layer. Is it going to be obvious to them when to use which pattern at what time? Are they going to need to be kept in the loop of what has(n't) been implemented in your layer? Because they'd presumably have to decide between patterns based on knowledge of whether the underlying entity is the same as the dto. Which means they need to know about the entity. Which means it's not actually separated by your layer. \$\endgroup\$ – Flater May 2 '18 at 12:51
  • 1
    \$\begingroup\$ The point I'm getting at is that when your separation is incomplete (as evidenced by your "for complex objects we are using the old pattern"), then you've created a need for consuming developers to understand your intermediary system while also still knowing the underlying basics. (There is a fairly relevant XKCD for this). Half of a separation is actually worse than no separation at all, since half of a separation would require both knowledge of the separated and unseparated parts. \$\endgroup\$ – Flater May 2 '18 at 12:54
  • 1
    \$\begingroup\$ And whilst I completely understand where you're coming from, questions about the overall architecture of our system fall out of scope of this specific code review. Undoubtedly it's worthy of a separate discussion on a different question, but it's not relevant to the code review at hand. \$\endgroup\$ – JChristen May 3 '18 at 6:27
5
\$\begingroup\$

You could use the ExpressionVisitor class to give yourself some more flexibility and not have to handle all the conditions yourself.

public class ExpressionConverter<TFrom, TTo> : ExpressionVisitor
    where TFrom : class, new() 
    where TTo : class, new()
{
    private readonly MappedConverter<TFrom, TTo> _converter;
    private ParameterExpression _fromParameter;
    private ParameterExpression _toParameter;

    public ExpressionConverter(MappedConverter<TFrom, TTo> converter)
    {
        _converter = converter;
    }

    public override Expression Visit(Expression node)
    {
        if (_fromParameter == null)
        {
            if (node.NodeType != ExpressionType.Lambda)
            {
                throw new ArgumentException("Expression must be a lambda");
            }

            var lambda = (LambdaExpression)node;
            if (lambda.ReturnType != typeof(bool) || lambda.Parameters.Count != 1 ||
                lambda.Parameters[0].Type != typeof(TFrom))
            {
                throw new ArgumentException("Expression must be a Func<TFrom, bool>");
            }

            _fromParameter = lambda.Parameters[0];
            _toParameter = Expression.Parameter(typeof(TTo), _fromParameter.Name);
        }
        return base.Visit(node);
    }

We going to have the ExpressionConverter inherit from the ExpressionVisitor and the first call to Visit make sure it's a Func lambda (Visit gets called a lot from recursion in the ExpressionTree) . If so we save the parameter expression we are converting from and making a new one for the one we are converting to.

Then override VisitParameter and swap out the parameters

protected override Expression VisitParameter(ParameterExpression node)
{
    if (_fromParameter == node)
    {
        return  _toParameter;
    }
    return base.VisitParameter(node);
}

Then we also need to override the VisitMembers to swap out member assignments

protected override Expression VisitMember(MemberExpression node)
{
    if (node.Expression == _fromParameter)
    {
        var member = _converter.GetMappingFromMemberName<TFrom>(node.Member.Name);
        return Expression.Property(_toParameter, member);
    }

    return base.VisitMember(node);
}

I did have to change the GetMappingFromMemberName to return the correct PropertyInfo instead of the mapping to make it work easier.

public PropertyInfo GetMappingFromMemberName<T>(string name)
{
    if (typeof(T) == typeof(Dto))
    {
        return this.Mappings.SingleOrDefault(x => x.DtoProperty.Name == name).EntityProperty;
    }
    else if (typeof(T) == typeof(Entity))
    {
        return this.Mappings.SingleOrDefault(x => x.EntityProperty.Name == name).DtoProperty;
    }
    throw new Exception($"Cannot get mapping for {typeof(T).Name} from MappedConverter<{typeof(Dto).Name}, {typeof(Entity).Name}>");
}

Last thing we need to do is create a new Expression<Func<TFrom, bool>>

protected override Expression VisitLambda<T>(Expression<T> node)
{
    if (typeof(T) == typeof(Func<TFrom, bool>))
    {
        return Expression.Lambda<Func<TTo, bool>>(Visit(node.Body), new[] { _toParameter }); 
    }
    return base.VisitLambda(node);
}

You can call it like so

Expression<Func<Model, bool>> lambda = x => x.LastName == x.FirstName  && x.FirstName == "John";
var newLambda = expressionConverter.Visit(lambda);

If you don't like the Visit call you can always make it an private class inside another class to give it a different method signature. But now you don't have to create the constants or operators. Just update the parameter and properties that are getting used. I didn't do CallMethod but if you need that you can just override Expression VisitMethodCall(MethodCallExpression node) using the same techniques as above.

\$\endgroup\$
  • \$\begingroup\$ Nice, didn't think to use the ExpressionVisitor. Only issue I had was converting multiple expressions with the same instance of the converter since the _fromParameter and _toParameter fields were referencing the Func variable from previous expressions, so you get an InvalidOperationException when swapping out the parameter expressions. That can be fixed by wrapping the Visit method in a ConvertExpression helper like above and resetting the fields whenever that is called \$\endgroup\$ – JChristen Jun 6 at 13:21
  • 1
    \$\begingroup\$ It does mean that it's not thread safe but by extracting the Visit logic into a separate subclass that you instantiate every time you convert an expression you get that back, and the code still remains nice and clean \$\endgroup\$ – JChristen Jun 6 at 13:23
  • \$\begingroup\$ Correct you would need to new up a class per time you need to use it as it has properties it needs to keep. \$\endgroup\$ – CharlesNRice Jun 6 at 13:26
  • \$\begingroup\$ @JChristen just realized you asked this question over a year ago when I got a badge for the answer. \$\endgroup\$ – CharlesNRice Jun 6 at 22:07
2
\$\begingroup\$

Can I say first off - this is excellent - both the post and the provided answer.

I tool follow a similar pattern of surfacing a DTO through my controllers and abstracting away my data access.

I have been looking for something like this for awhile now and this has helped me out.

Taking what @CharlesNRice provided using the ExpressionVisitor - I added yet another little change to the MappedConverter implemented.

Using Automapper and it seems that the

public class Mapping<Dto, Entity>
{
  public PropertyInfo DtoProperty { get; }
  public PropertyInfo EntityProperty { get; }
}

Could be replaced by using Automapper and referencing the mapping configuration that is registered.

I changed the Mapped Converter to to a IPropertyMappingProvider

    public interface IPropertyMappingProvider
    {
        IPropertyMapping GetPropertyMapping<TSource, TDestination>(string sourcePropertyName);
    }

With the IPropertyMapping the exact same thing as the Mapping you have implemented.

    public interface IPropertyMapping
    {
        PropertyInfo SourceProperty { get; }
        PropertyInfo DestinationProperty { get; }
    }

The implementation of the PropertyMappingProvider simply injects the IMapper instance that is registered with the application. We can then use the IMapper to get the property mapping configuration.

 public class PropertyMappingProvider : IPropertyMappingProvider
{
    private readonly IMapper _mapper;

    public PropertyMappingProvider(IMapper mapper)
    {
        _mapper = mapper;
    }

    /// <summary>
    /// Returns a <see cref="IPropertyMapping"/> mapped properties from the IMapper configuration based on the source property name
    /// </summary>
    /// <typeparam name="TSource">Source mapping class</typeparam>
    /// <typeparam name="TDestination">Destination mapping class</typeparam>
    /// <param name="sourcePropertyName">The property name on the source class</param>
    /// <returns><see cref="IPropertyMapping"/> Contains the <see cref="PropertyInfo"/> classes for <typeparam name="TSource"></typeparam> property and <typeparam name="TDestination"></typeparam></returns>
    public virtual IPropertyMapping GetPropertyMapping<TSource, TDestination>(string sourcePropertyName)
    {
        var configurationProvider = _mapper.ConfigurationProvider;

        var mapping = configurationProvider.FindTypeMapFor<TSource, TDestination>();

        var propertyMap = mapping.PropertyMaps.FirstOrDefault(pm => pm.SourceMember.Name == sourcePropertyName);

        if(propertyMap == null)
            throw new ArgumentException($"No mappings found for {sourcePropertyName}");

        if(propertyMap.SourceMember.MemberType != MemberTypes.Property)
            throw new ArgumentException($"{sourcePropertyName} is not a property {nameof(TSource)}");

        if(propertyMap.DestinationMember.MemberType != MemberTypes.Property)
            throw new ArgumentException($"{propertyMap.DestinationMember.Name} is not a property of {nameof(TDestination)}");

        var sourcePropertyInfo = (PropertyInfo) propertyMap.SourceMember;

        var destinationPropertyInfo = (PropertyInfo) propertyMap.DestinationMember;

        return new PropertyMapping(sourcePropertyInfo, destinationPropertyInfo);
    }
}

We can get to the PropertyInfo objects on the mapped properties.

Using it is just as you have done in your ExpressionVisitor implementation:

 public sealed class ExpressionConverter<TFrom, TTo> : ExpressionVisitor, IExpressionConverter<TFrom, TTo>
{
    private readonly IPropertyMappingProvider _propertyMappingProvider;
    private ParameterExpression _fromParameter;
    private ParameterExpression _toParameter;

    public ExpressionConverter(IPropertyMappingProvider propertyMappingProvider)
    {
        _propertyMappingProvider = propertyMappingProvider;
    }

    public Expression<Func<TTo, bool>> Convert(Expression<Func<TFrom, bool>> expression)
    {
        var expr =  Visit(expression);

        return expr.Convert<TTo>();
    }

...

 protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression != _fromParameter) return base.VisitMember(node);

        var member = _propertyMappingProvider.GetPropertyMapping<TFrom, TTo>(node.Member.Name);

        return Expression.Property(_toParameter, member.DestinationProperty);
    }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.