1

I'm using EntityFramework 6.0, DB first. In my data layer I access my database to get the results.

using(ModelContext m = new ModelContext())
{
    var results = m.Person
        .Include(p => p.Address)
        .Include(p => p.Orders)
        .ToList();

    responde.Results = results.Select(PersonToDto);
}

Now, the code above is selecting all the columns in the three tables and bringing in way more information than I need and I'm not mapping all of it in the PersonToDto() method.

So now I'm trying to use a new approach that allows me to select only the columns I want while preserving the mapper.

using(ModelContext m = new ModelContext())
{
    var results = m.Person
        .Include(p => p.Address)
        .Include(p => p.Orders)
        .Select(PersonToDtoExpr);
}

public static Expression<Func<Person, PersonDto>> PersonToDtoExpr()
{
    return person => new PersonDto
        {
             Name = person.Name,
             Email = person.Email,
             Orders = person.Select(p => p.Orders).Select(OrdersToDto)
        }
}

This works up to this point, because I have a 1 to * relationship from Person to Orders, but when I try to do the same for the Address I have a problem, I don't have a select statement to use the other Expression in.

I could do the mapping for the Address directly on the Person mapper, but I have around 60 places where I need to map the Address and I don't want to have to repeat that code everywhere. This would also allow me to basically do the selection of the specific fields I want from the DB while doing the mapping at the same time, segregated in the data layer and reusable in other parts of the code.

Is there a way to map that single object inside of that expression in a way that is usabl in Linq to SQL?

I've been Reading about expression trees, but it's a little too abstract for me to grasp what is happening.

MasterOfTwo
  • 101
  • 9
  • This is so much easier with AutoMapper. – Gert Arnold Dec 07 '17 at 11:43
  • AutoMapper has been pondered, but does it actually only select the fields you want? I'm not very familiar with how it works, but how easy is it to define your own mappings? – MasterOfTwo Dec 07 '17 at 12:13
  • [Yes it does](https://stackoverflow.com/a/12365931/861716). – Gert Arnold Dec 07 '17 at 12:18
  • Then it's likely I'll be using AutoMapper in the neat future. But under the hood AutoMapper is probably doing something similar to what I'm trying to do, so that probably means there's a way to do it. – MasterOfTwo Dec 07 '17 at 12:26

1 Answers1

0

This can be achieved with the help of LinqKit. You can add LinqKit to your project as a NuGet or you can download the source code. I'll skip over exactly what LinqKit does or how it does it (more info in the link) but it basically allows you to convert invocations to an expression into a proper linq2sql expression.

Using the example in the question:

using(ModelContext m = new ModelContext())
{
    //use either like this
    var results = m.Person.AsExpandable()
        .Include(p => p.Address)
        .Include(p => p.Order)
        .Select(PersonToDtoExpr);

    //or like this
    var results = m.Person
        .Include(p => p.Address)
        .Include(p => p.Order)
        .Select(PersonToDtoExpr().Expand());
}

public static Expression<Func<Person, PersonDto>> PersonToDtoExpr()
{
    //Instantiate the expression. This is important!
    Expression<Func<Order, OrderDTO>> OrdersExpr = OrderToDtoExpr();

    return person => new PersonDto
    {
         Name = person.Name,
         Email = person.Email,
         //This Invoke must be the extension method from LinqKit
         Orders = person.Order.Select(o => OrdersExpr.Invoke(o)).ToList()
    }
}

A couple more notes. As mentioned in the comments, you need to either use .AsExpandable() on your query or .Expand() on the expression so LinqKit can clean up those .Invoke().

Also, make sure you use the extension method Invoke() from LinqKit and not the one the Expressions namespace.

Finally, this method can also be used for single complext types:

public static Expression<Func<Person, PersonDto>> PersonToDtoExpr()
{
    //Instantiate the expression in the exact same way
    Expression<Func<Order, OrderDTO>> OrdersExpr = OrderToDtoExpr();

    return person => new PersonDto
    {
         Name = person.Name,
         Email = person.Email,
         //This Invoke must be the extension method from LinqKit
         Order = OrdersExpr .Invoke(person.Order)
    }
}
MasterOfTwo
  • 101
  • 9