1

So I'm using the AutoMapper library, and I'm trying to understand how AutoMapper is mapping from the source to the destination property. I think I'm just missing a key part of C#'s expression syntax/Linq support. If anyone could explain to me what exactly is happening here I would appreciate it.

Here is an example of some AutoMapper configuration code:

Mapper.CreateMap<SourceModel, DestinationModel>()
                .ForMember(dest => dest.Summary, opt => opt.MapFrom(src => src.Summary))
                .ForMember(dest => dest.Year, opt => opt.MapFrom(src => src.Year));

I'm most perplexed by the line dest => dest.Summary... How exactly is src.Summary being mapped in to dest.Summary? How does evaluating the expression dest.Summary tell AutomMapper to map in to this property? I'm guessing there is some powerful lambda/expression features that I don't understand or am missing.

Thanks!

DigitalZebra
  • 39,494
  • 39
  • 114
  • 146
  • It may be helpful to look at the AutoMapper source code, available at https://github.com/AutoMapper/AutoMapper. – Magnus Grindal Bakken Aug 27 '13 at 20:37
  • This link may also be helpful http://www.codeproject.com/Articles/61629/AutoMapper – Bit Aug 27 '13 at 20:38
  • `ForMember` operates on the types you specify as the constructor generic parameters - `SourceModel` and `DestinationModel`. It is just convention to use the lambda argument names of `dest` and `src`. The `ForMember` declarations you specified are not necessary as automapper will automagically map public properties with same name and implicit type conversion (i.e. if `Year` is an `int` in both `SourceDestination` and `DestinationModel`). – Jay Walker Aug 27 '13 at 21:02
  • 2
    Expression.Compile is a magical, magical thing. You can compile expressions, then invoke them as functions later on. – Jimmy Bogard Aug 27 '13 at 21:10

2 Answers2

2

Automapper is simply getting information on the types you wish to map and the PropertyInfo of the properties to be matched. If you would like to understand what it is doing, try to play around a little bit with this:

public static class PropertyExtensions
{
    public static System.Reflection.PropertyInfo ToPropertyInfo<T, TProperty>(this Expression<Func<T, TProperty>> propExpression) where T : class
    {
        if (propExpression == null)
            throw new ArgumentNullException("propExpression");
        var expr = propExpression.Body as MemberExpression;
        if (expr == null)
            throw new ArgumentException("Property expression is not a member expression.", "propExpression");
        var propInfo = expr.Member as System.Reflection.PropertyInfo;
        if (propInfo == null)
            throw new ArgumentException("Member expression is not for a property.", "propExpression");
        return propInfo;
    }
}
public static class TestIt<T> where T : class
{
    public static void PrintProperty<TProperty>(Expression<Func<T, TProperty>> propExpression)
    {
        Console.WriteLine(propExpression.ToPropertyInfo());
    }
}

class Program
{
    static void Main(string[] args)
    {
        TestIt<string>.PrintProperty(s => s.Length);
    }
}
Alex
  • 13,024
  • 33
  • 62
1

It's a bit unclear specifically what you're asking, there are several parts:

  • The Fluent Interface. When you call CreateMap() it will return something like IFluentInterface<TSource, TDestination>. This is the main interface to define mappings. Most members of this interface will return this interface as well, which allows you to make several .ForMember() calls

    • The CreateMap() call also sets the Generic types. The types you pass will be applied to all relevant members of the interface, giving you a strongly-typed interface (a "closed generic"). Without this calls like dest => dest.Summary would not be possible (you'd probably have to specify them by name, which is fragile).
  • The ForMember() call takes two parameters.

    • The first is most likely an Expression<Func<TDestination, object>>. This is a mouthful, but it specifies that you can provide a Lamdda expression against the TDestination type (closed by the preceding CreateMap() call) that retuns an object. Within this method it's likely resolving the specified object to a MemberInfo, which lets automapper retrieve the metadata and value of the specified member.

    • The second member is another AutoMapper type; it will be a type used to instruct AutoMapper how to set the value for the specified member. It could be provided an argument such as the opt.MapFrom<TSource>() call in your example, or probably others to do things like use a constant value.

These calls are likely used during AutoMapper initialization to define the configuration for the mapping from one type to another. Basically these are instructions; AutoMapper may or may not have a seperate set of code that follows these instructions to perform the actual mapping.


Here's another SO question that shows how the internals of calls like ForMember(dest => dest.Summary) are possibly implemented. This covers looking up the MemberInfo of the specified member.

Here's some basic info on Fluent Interfaces/

Community
  • 1
  • 1
STW
  • 44,917
  • 17
  • 105
  • 161