Pass Objects to AutoMapper Mapping

2019-01-17 00:28发布

问题:

I am working with AutoMapper and some of the values for the entity being mapped to are variables in my current method. I have tried to Google it but to no avail. Can I pass a set of KeyValue Pairs or an object or something to my mapping to have it use those values?

Sample of Post Mapping Modification

//comment variable is a Comment class instance
var imageComment = AutoMapper.Mapper.Map<Data.ImageComment>(comment);
//I want to pass in imageId so I dont have to manually add it after the mapping
imageComment.ImageId = imageId;

回答1:

AutoMapper handles this key-value pair scenario out of the box.

Mapper.CreateMap<Source, Dest>()
    .ForMember(d => d.Foo, opt => opt.ResolveUsing(res => res.Context.Options.Items["Foo"]));

Then at runtime:

Mapper.Map<Source, Dest>(src, opt => opt.Items["Foo"] = "Bar");

A bit verbose to dig into the context items but there you go.



回答2:

For Automapper 6.0.2:

Profile:

public class CoreProfile : Profile
{
    public CoreProfile()
    {
        CreateMap<Source, Dest>()
            .ForMember(d => d.Foo,
                opt => opt.ResolveUsing(
                    (src, dst, arg3, context) => context.Options.Items["Foo"]
                )
            );
    }
}

Mapping:

    var result = Mapper.Map<PlanResult>(aa, opt => {
        opt.Items["Foo"] = 2;
        opt.Items["OtherFoo"] = 1000;
    });


回答3:

Objects can be passed to the resolver with the Items Dictionary option. The standard API to do this is pretty verbose (as seen in the accepted answer) but can be simplified nicely using a few extension methods:

/// <summary>
/// Map using a resolve function that is passed the Items dictionary from mapping context
/// </summary>
public static void ResolveWithContext<TSource, TDest, TMember, TResult>(
    this IMemberConfigurationExpression<TSource, TDest, TMember> memberOptions, 
    Func<TSource, IDictionary<string, object>, TDest, TMember, TResult> resolver
) {
    memberOptions.ResolveUsing((src, dst, member, context) => resolver.Invoke(src, context.Items, dst, member));
}

public static TDest MapWithContext<TSource, TDest>(this IMapper mapper, TSource source, IDictionary<string, object> context, Action<IMappingOperationOptions<TSource, TDest>> optAction = null) {
    return mapper.Map<TSource, TDest>(source, opts => {
        foreach(var kv in context) opts.Items.Add(kv);
        optAction?.Invoke(opts);
    });
}

Which can be used like this:

// Define mapping configuration
Mapper.CreateMap<Comment, ImageComment>()
    .ForMember(
        d => d.ImageId,
        opt => opt.ResolveWithContext(src, items, dst, member) => items["ImageId"])
    );

// Execute mapping
var context = new Dictionary<string, object> { { "ImageId", ImageId } };
return mapper.MapWithContext<TSource, TDest>(source, context);

If you have an object that you commonly need to pass to mapper resolvers (for example, the current user), you can go one step further and define more specialized extensions:

public static readonly string USER_CONTEXT_KEY = "USER";

/// <summary>
/// Map using a resolve function that is passed a user from the
/// Items dictionary in the mapping context
/// </summary>
public static void ResolveWithUser<TSource, TDest, TMember, TResult>(
    this IMemberConfigurationExpression<TSource, TDest, TMember> memberOptions,
    Func<TSource, User, TResult> resolver
) {
    memberOptions.ResolveWithContext((src, items, dst, member) =>
        resolver.Invoke(src, items[USER_CONTEXT_KEY] as User));
}

/// <summary>
/// Execute a mapping from the source object to a new destination
/// object, with the provided user in the context.
/// </summary>
public static TDest MapForUser<TSource, TDest>(
    this IMapper mapper,
    TSource source,
    User user,
    Action<IMappingOperationOptions<TSource, TDest>> optAction = null
) {
    var context = new Dictionary<string, object> { { USER_CONTEXT_KEY, user } };
    return mapper.MapWithContext(source, context, optAction);
}

Which can be used like:

// Create mapping configuration
Mapper.CreateMap<Source, Dest>()
    .ForMember(d => d.Foo, opt => opt.ResolveWithUser((src, user) src.Foo(user));

// Execute mapping
return mapper.MapWithUser(source, user);


回答4:

Suppose you have these two objects:

public class ObjectA {
 public string Property1 { get; set; }
 public int Property2 { get; set; }
}
public class ObjectB {
 public string Property1 { get; set; }
 public int Property2 { get; set; }
}

And you want to copy an existing object of type ObjectA into a new object of type ObjectB, using AutoMapper you have to do this:

var objectA = new ObjectA { Property1 = "Hello, World!", Property2 = 1 }
var objectB = new ObjectB();

// Copy data from a to b
AutoMapper.Mapper
  .CreateMap<ObjectA, ObjectB>()
  .BeforeMap((source, dest) => { dest.ImageId = imageId });
AutoMapper.Mapper.Map(objectA, objectB); // source, destination;

// Or:
var objectB = AutoMapper.Mapper.Map<ObjectB>(objectA);