Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping DTO classes with Expression<Func> inside another Expression<Func>

I'm working with the Entity Framework Code First and trying to map from my entity classes to my DTO classes. But I'm having difficult figuring out how to write the Selector.

In this small example, I have created a Person class and a Address class.

In the DTO classes I have created a Selector, which maps from my Entity to my DTO, but is it not possible to use AddressDto.Selector inside the PersonDto.Selector?

public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        public int Id { get; set; }
        public string Street { get; set; }
    }

Now I'm trying to map this to a DTO classes.

    public class PersonDto
        {
            public static Expression<Func<Person, PersonDto>> Selector = 
            entity => new PersonDto
                {
                     Id = entity.Id,
                     Name = entity.Name,
                     Address = ??? AddressDTO.Selector
                };

            public int Id { get; set; }
            public string Name { get; set; }
            public AddressDto Address { get; set; }
        }

        public class AddressDto
        {
            public static Expression<Func<Address, AddressDto>> Selector = 
            entity => new AddressDto
                 {
                     Id = entity.Id,
                     Street = entity.Street
                 };

            public int Id { get; set; }
            public string Street { get; set; }
        }

I know that I could just write this inside PersonDto.Selector

Address = new AddressDto
                     {
                         Id = entity.Address.Id,
                         Street = entity.Address.Street
                     };

But I'm looking for a way to reuse the Selector from the AddressDto class. To keep the code clean and separate responsibility between classes.

like image 627
Anders Gulbæk Avatar asked Dec 05 '13 20:12

Anders Gulbæk


1 Answers

So, we're going to need several helper methods here, but once we have them, things should be fairly simple.

We'll start out with this class that can replace all instance of one expression with another:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

Then an extension method to make calling it easier:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Next we'll write a compose extension method. This will take a lambda that computes an intermediate result, and then another lambda that computes a final result based on the intermediate result and returns a new lambda that takes what the initial lambda returns and returns the output of the final lambda. In effect it calls one function, and then calls another on the result of the first, but with expressions, not methods.

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

Now we'll create a Combine method. This will be similar, but subtly different. It will take a lambda that computes an intermediate result, and a function that uses both the initial input, and the intermediate input, to compute a final result. It's basically the same as the Compose method, but the second function also gets to know about the first parameter:

public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

Okay, now that we have all of this, we get to use it. First thing we'll do is create a static constructor; we won't be able to inline all we have to do into the field initializer. (The other option is to make a static method that computes this, and have the initializer call it.)

After that we'll create an expression that take a person and returns it's address. It's one of the missing puzzle pieces in the expressions that you have. Using that, we'll compose that address selector with the AddressDto selector, and then use Combine on that. Using that we have a lambda that takes a Person and an AddressDTO and returns a PersonDTO. So in there we have basically what you wanted to have, but with an address parameter given to us to assign to the address:

static PersonDto()
{
    Expression<Func<Person, Address>> addressSelector =
        person => person.Address;

    Selector = addressSelector.Compose(AddressDto.Selector)
            .Combine((entity, address) => new PersonDto
            {
                Id = entity.Id,
                Name = entity.Name,
                Address = address,
            });
}
like image 98
Servy Avatar answered Nov 14 '22 04:11

Servy