Is there a way to map Func<T1, bool> to Func<T2, bool>?

Viewed 110

So, I was wondering if it possible to do the next thing in c#:

I have a DB model - let's say it is Car:

public class Car {
  public string Id {get;set;}
  public string Name {get;set}
}

And a DbSet for this type in someDbContext:

public DbSet<Car> Cars {get;set;}

And also I have a CarDto

public class CarDto {
  public string Id {get;set;}
  public string Name {get;set}
}

And as result we get something like this:

var select = new Func<CarDto, bool>(car => car.Name == "BMW");

// And somehow use this expression for other type Car
someDbContext.Cars.Where(select);

Maybe there is an approach in which I could map these Funcs like this:

var newFunc = mapper.Map<Func<Car, bool>>(select);

Any thoughts?

2 Answers

If you just want to handle rewriting property accesses, you can use an ExpressionVisitor which looks a bit like this:

public class Program
{
    public static void Main()
    {
        Expression<Func<Car, bool>> expr = x => x.Name == "BMW";
        var replaced = ReplaceParameter<CarDto>(expr);
    }

    private static Expression<Func<T, bool>> ReplaceParameter<T>(LambdaExpression expr)
    {
        if (expr.Parameters.Count != 1)
            throw new ArgumentException("Expected 1 parameter", nameof(expr));

        var newParameter = Expression.Parameter(typeof(T), expr.Parameters[0].Name);
        var visitor = new ParameterReplaceVisitor()
        {
            Target = expr.Parameters[0],
            Replacement = newParameter,
        };
        var rewrittenBody = visitor.Visit(expr.Body);
        return Expression.Lambda<Func<T, bool>>(rewrittenBody, newParameter);
    }
}

public class ParameterReplaceVisitor : ExpressionVisitor
{
    public ParameterExpression Target { get; set; }
    public ParameterExpression Replacement { get; set; }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == this.Target)
        {
            // Try and find a property with the same name on the target type
            var members = this.Replacement.Type.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Public | BindingFlags.Instance);
            if (members.Length != 1)
            {
                throw new ArgumentException($"Unable to find a single member {node.Member.Name} of type {node.Member.MemberType} on {this.Target.Type}");
            }
            return Expression.MakeMemberAccess(this.Replacement, members[0]);
        }

        return base.VisitMember(node);
    }
}

We need to deconstruct the LambdaExpression into its body and parameters. We need to create a new parameter which has the correct type, and replace all usages of the old parameter with the new one. This is where the visitor comes in: whenever it sees you access a member on the old parameter, it tries to find the corresponding member on the new parameter, and access that instead.

We then construct a new LambdaExpression, using the rewritten body and the new parameter.

You have a whole bunch of options:

  1. Derive your Dto class from the context class. That way you can use polymorphism as normal.

  2. Extract an interface and implement it in both your Dto and context classes. Same as above then, use polymorphism.

  3. Use duck-typing. In C#, that's done with the dynamic keyword. You lose Intellisense and compile-time error checking, but your code will work.

  4. Reflection. It's a lot of code, it's slow, it's practically a much worse version of #3, but you can cobble it together if you really try.

Something like Automapper will help you map your context to your Dto piece-wise, but it won't help you translate your lambda function filters.

Related