Projecting computed properties with LINQ and AutoMapper

Another from the “wicked awesome” category of AutoMapper. Other blogs about AutoMapper and LINQ:

One current limitation of ORM LINQ projections in general is that it can’t project computed properties:

public class Customer {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string FullName { get { return FirstName + " " + LastName; } }
}

public class CustomerDto {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string FullName { get; set; }
}

Mapper.Map<Customer, CustomerDto>(customer); // this works

dbContext.Customers.Project().To<CustomerDto>().ToList(); // this fails

If you try this using LINQ, you’ll get some error in your LINQ provider about not being able to use a computed property in an expression. Your options at this point are to duplicate the computed property on the destination type, to switch over to the regular Mapper.Map call, or modify your ORM such that the database column itself is defined as a computed column. All of these are fairly intrusive.

But wait, all is not lost! What if we could peer inside the computed property, examine what it’s doing, and include that in our LINQ projection? That sounds hard, but luckily someone has already figured it out in the DelegateDecompiler project.

So how might we put this together? First, we’ll reference the NuGet package for the DelegateDecompiler project

Install-Package DelegateDecompiler

Next, let’s create an extension method for AutoMapper to make it easy to wrap our projection calls with the delegate decompilation:

public static class MapperExtensions {
    public static List<TDestination>
        ToList<TDestination>(this IProjectionExpression projectionExpression)
    {
        return projectionExpression.To<TDestination>().Decompile().ToList();
    }
}

See that extra “Decompile” method? That’s modifying our existing expression tree that AutoMapper built and replacing computed properties with our own. Underneath the covers, it’s taking this LINQ expression:

.Select(customer => new CustomerDto {
    FirstName = customer.FirstName,
    LastName = customer.LastName,
    FullName = customer.FullName
});

And replacing it with this:

.Select(customer => new CustomerDto {
    FirstName = customer.FirstName,
    LastName = customer.LastName,
    FullName = customer.FirstName + " " + customer.LastName
});

The only change to our models was to indicate to the delegate decompiler that this is a computed property. In our domain model, we decorate with the Computed attribute, and now everything works!

public class Customer {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  [Computed]
  public string FullName { get { return FirstName + " " + LastName; } }
}
 
public class CustomerDto {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string FullName { get; set; }
}
 
dbContext.Customers.Project().ToList<CustomerDto>(); // this works!

Looking at the underlying SQL generated, since the LINQ provider can understand that LINQ expression, we’ll see something like:

SELECT
  c.FirstName,
  c.LastName,
  c.FirstName + ' ' + c.LastName AS [FullName]
FROM Customer c

I can build computed properties on my domain model, project them automatically using AutoMapper, and have computed properties evaluated all the way down at the database tier with that opt-in “Computed” attribute.

That, I think, is wicked awesome.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in AutoMapper. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Sławek
    • jbogard

      I like my way better, less intrusive (just an attribute and an extra call in my IQueryable)

  • marcusswope

    Would be awesome to allow the user to specify their own predicate on when to apply the Decompile. Like “always on properties with no setters” or “always on properties with the Computed attribute”. But, I agree this is awesome.

  • Mike Cole

    Sexy.

  • Stéphane Jutin

    Should the computed properties be defined on the DTO rather than the entity ? For me this is clearly a presentation concern what label I want to display in a combo box when i select an entity.

    • jbogard

      I’m not sure a label is a computed property? I put those as data annotations on my view model.