AutoMapper 3.3 feature: parameterized projections

Back in AutoMapper 3.1, I added a feature to allow for runtime values to be used in mappings:

Mapper.Initialize(cfg => {
    cfg.CreateMap<Src, Dest>()
        .ForMember(d => d.UserName,
           opt => opt.ResolveUsing(ctx => ctx.Items["CurrentUserName"]));
});

// In my controller action

var model = Mapper.Map<Src, Dest>(src,
   opt => opt.Items["CurrentUserName"] = User.Identity.Name);

This worked great for runtime mappings, but wasn’t supported in the LINQ projections. These days, I’m pretty much solely using the LINQ projections in concert with some sort of data access object/ORM (ISession, DbContext, IDocumentSession etc.).

Allowing for runtime values in LINQ queries is fairly straightforward without AutoMapper – you can create a closure that gets captured in the resulting expression tree:

dbContext.WorkItems.Select(src => {
    Name = src.Name,
    User = User.Identity.Name
});

The resulting closure “helper” class effectively allows for a parameterized query. So how might we accomplish this with AutoMapper? Our first thought might be to use the “MapFrom” method with the runtime value supplied:

Mapper.CreateMap<Src, Dest>()
   .ForMember(d => d.UserName, opt => opt.MapFrom(/* ????? */));

But the problem here is mapping definitions are static, defined once and reused throughout the lifetime of the application. Before 3.3, you would need to re-define the mapping on every request, with the hard-coded value. And since the mapping configuration is created in a separate location than our mapping execution, we need some way to introduce a runtime parameter in our configuration, then supply it during execution.

This is accomplished in two parts: the mapping definition where we create a runtime parameter, then at execution time when we supply it. To create the mapping definition with a runtime parameter, we “fake” a closure that includes a named local variable:


Mapper.Initialize(cfg => {

   string userName = null;
   cfg.CreateMap<Source, Dest>()
        .ForMember(d => d.UserName, 
            opt => opt.MapFrom(src => userName)
        );
});

We can’t access the “real” value we’d use during execution in our configuration, so we create a stand-in that still creates a closure for us. The underlying expression tree that gets built recognizes this external input and creates a placeholder parameter to be supplied at runtime. When executing the projection, we can supply our parameter value with either a dictionary or an anonymous object:

dbContext.Sources
   .Project().To<Dest>(new {userName = User.Identity.Name})
   .ToList();

// Dictionary fun
dbContext.Sources
   .Project().To<Dest>(new Dictionary<string, object> {["userName"] = User.Identity.Name})
   .ToList();

When the projection is executed by the underlying LINQ provider, the correct runtime value is replaced in the expression, letting you use per-map runtime values in your projections. You can use these runtime values in any configuration option that works off an expression:

  • MapFrom
  • ConstructProjectionUsing

Very cool stuff!

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.
  • Daniel Mackay

    Great work Jimmy. Slightly unrelated question – what’s the difference between using Mapper.Initialize() and Mapper.CreateMap() to configure your maps?

    • jbogard

      Mapper.Initialize is the preferred method – it optimizes everything at the end of the Initialize call.

  • Nice! I’ve caught myself wishing for exactly this on numerous occasions!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1768()

  • Paul

    Great feature, but is it available when mapping a single item as well as a collection

    • jbogard

      It’s available for any IQueryable. You can do dbContext.Products.Project().To().SingleOrDefault(), and there’s your one item.

  • John Landheer

    There is probably a good reason, but why not be able to create mappings with extra types?

    cfg.CreateMap()
    .ForMember(d => d.UserName,
    opt => opt.MapFrom((src, options) => scr.UserName + options.Domain)
    );

    • For a single object, I ended up having to do:


      var dest = new[] { srcObj }.AsQueryable().Project().To(new
      {
      userName = User.Identity.Name
      }).Single();

      Is there an easier way that I am missing?

      • jbogard

        It is available for Mapper.Map, use a value resolver and the mapping operation option’s Items dictionary.

        • AJ

          On a certain object, I need to use both Mapper.Map and in other places, Project. Will this work for both methods?

          • jbogard

            No. Map uses value resolvers and the ResolutionContext object, while ProjectTo doesn’t have any context object (no query provider could interpret a value resolver).