How to use mapper.Map inside MapperConfiguration of AutoMapper? - c#

I need to map an object to another one using AutoMapper. The tricky question is how can I access an instance of the mapper (instance of IMapper) inside of the mapping configuration or inside of a custom type converter?
The code below does not work, however it is an example of what I would like to achieve - please notice the mapper.Map calls and assume that mappings Customer => CustomerDto and Customer => DetailedCustomerDto are defined.
var config = new MapperConfiguration(
cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing(o => {
return o.Type == 1
? mapper.Map<Customer, CustomerDto>(o.Customer)
: mapper.Map<Customer, DetailedCustomerDto>(o.Customer)
})
);
The client part is:
var mapper = config.CreateMapper();
var orderDto = mapper.Map<Order, OrderDto>(order);
The simplified version of objects I want to map is:
public class Order
{
public int Type { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public long Id { get; set; }
public string Name { get; set; }
}
public class OrderDto
{
public CustomerDto Customer { get; set; }
}
public class CustomerDto
{
public long Id { get; set; }
}
public class DetailedCustomerDto : CustomerDto
{
public string Name { get; set; }
}
As you see from the code above, based on the value of Order.Type, the mapper should map the property Order.Customer to different targets. As one target (DetailedCustomerDto) inherits from the other one (CustomerDto) it becomes a bit tricky.
Please notice that usage of the obsolete and deprecated static method Mapper.Map is NOT an option.

As of AutoMapper 8.0 and up
The answer below for 5.1.1 still applies, but note that the use of ResolveUsing has been replaced with an overload of MapFrom, but the signature has otherwise remained consistent.
As of AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with four parameters, fourth of which is ResolutionContext (context.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((order, orderDto, i, context) => {
return order.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(order.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
Prior to AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with two parameters, first of which is ResolutionResult (result.Context.Engine.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((result, order) => {
return order.Type == 1
? result.Context.Engine.Mapper.Map<Customer, CustomerDto>(order.Customer)
: result.Context.Engine.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));

In addition to Evk's great answer, which helped me, if you need to do a mapping inside a mapping inside a config/profile that requires a custom constructor (i.e. the type has no default constructor), the following will work in v5.2.0:
CreateMap<Models.Job, Models.API.Job>(MemberList.Source);
CreateMap<StaticPagedList<Models.Job>, StaticPagedList<Models.API.Job>>()
.ConstructUsing((source, context) => new StaticPagedList<Models.API.Job>(
context.Mapper.Map<List<Models.Job>, List<Models.API.Job>>(source.ToList()),
source.PageNumber,
source.PageSize,
source.TotalItemCount));
In this example I'm mapping the X.PagedList custom collection type of one object type onto an equivalent collection of another object type. The first parameter to the lamdba expression is your source object, the second is your ResolutionContext from which you can access a mapper instance to map from.

I'm using Automapper 9 and the answers above didn't work for me.
Then for resolve my problem that is like yours I use .afterMap, like that:
public class AutoMapperOrder : Profile
{
public AutoMapperOrder()
{
CreateMap<Customer, CustomerDto>()
//...
CreateMap<Customer, DetailedCustomerDto>()
//...
CreateMap<Order, OrderDto>()
.AfterMap((src, dest, context) => {
dest.Customer = src.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(src.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(src.Customer)
}
}
}
}
I hope to help somebody.

Related

Automapper ignore all mapping where value is null or default value

I'm using automapper via DI and want to have generic rules for all the mappings found in my solution. Belows example is of a single class, however I've got hundreds of classes to maintain and therefore want to add it to the mapper, not the mapping profile.
So far, I can handle Null values as follows :
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
Which in my example below, allows for the null update to persist past all the subsiquent updates. What I want is a generic rule ( or statement) for the default value of Guids, DateTimeOffset and Int. In my example below, update 1 and 2 are lost once update 3 is mapped.
using System;
using Autofac;
using AutoMapper;
namespace AutoMapperProblem
{
public class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterInstance(new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(Program).Assembly);
cfg.ForAllMaps((obj, cnfg) =>
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}).CreateMapper()).As<IMapper>().SingleInstance();
var container = builder.Build();
var mapper = container.Resolve<IMapper>();
var MainObject = new MainObject();
//this update persists pass all updates
var updateNull = new UpdateObject { NullGuid = Guid.NewGuid() };
mapper.Map(updateNull, MainObject);
var update1 = new UpdateObject { DateTimeOffset = DateTimeOffset.Now };
mapper.Map(update1, MainObject);
var update2 = new UpdateObject { Guid = Guid.NewGuid() };
mapper.Map(update2, MainObject);
var update3 = new UpdateObject { Int = 10 };
mapper.Map(update3, MainObject);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MainObject, UpdateObject>()
.ReverseMap();
}
}
public class MainObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
public class UpdateObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
}
I want it to work based on the type, eg ints, DateTimeOffsets and Guid's .
Heres a working example on DotNetFiddle : https://dotnetfiddle.net/Zh0ta6
Thanks for any help with this
The problem is that you cannot check value types against null in your opts.Condition() check, as they always have a value. Also, you can't use the default keyword directly in this specific context because the variable type is object. That will turn default to null again, and fail.
Depending on what you want to do, you can check if the destination already have a non-default value and do NOT replace it with any source value, which might be the default value of the type (0 for int, 0000...-000 for Guid, etc.). The check could look like this:
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember, destMember) =>
{
object destDefaultValue = null;
if (destMember != null)
{
Type destType = destMember.GetType();
if (destType.IsValueType)
{
destDefaultValue = Activator.CreateInstance(destType);
}
else
{
destDefaultValue = null;
}
}
bool destinationHasNoValue = Object.Equals(destMember, destDefaultValue);
return destinationHasNoValue;
})
The first part is calculating the default value for the given destination value, based on the type of the destination value. The latter part is checking if the destination already have a non-default value or not. Consecutive calls of Map() with the same target object will fill the "missing" property values until all the property values are set (in this example). If necessary, check the srcMember value and it's default type as well to fine-tune when the value should be copied or not.

Automapper adding valueTransformer to the "dictionary to object" mapper

I am trying to map a Dictionary to a POCO with the builtin mapper and apply an additional transformation on strings with a global ValueTransformer:
class MyPoco
{
public string StringValue { get; set; }
}
The mapper call:
var source = new Dictionary<string, object>
{
{ "StringValue", "abc" }
};
var mapper = new MapperConfiguration(cfg =>
{
cfg.ValueTransformers.Add<string>(dest => dest + "!!!");
})
.CreateMapper();
var poco = mapper.Map<MyPoco>(source);
Assert.Equal("abc!!!", poco.StringValue);
After the mapping call the poco.StringValue equals to 'abc' and the configured ValueTransfomer is never called. Am I missing something?

How to populate one extra property in a .Select - LINQ [duplicate]

This question already exists:
How to solve Npgsql.NpgsqlOperationInProgressException: A command is already in progress
Closed 3 years ago.
I'm using my Map method to create DTO object from my context class Company and it looks like this:
private CompDTO Map(Company company)
{
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
};
}
CompDTO looks like this:
public class CompDTO
{
public long Id { get; set; }
public string Title { get; set; }
public long? ParentCompanyId { get; set; }
public bool HasChildrens { get; set; }
}
I'm using it like this, basically receiving list of companies and calling another Map method which would create DTO object from my company objects and main issue for me is that Company class does not contain HasChildrens property, so I have to populate it somehow, and I couldn't do it where I'm maping other props because there I dont have access to a companies list.
private IEnumerable<CompDTO> Map(IEnumerable<Company> companies)
{
// Mapping all properties except HasChildrens because it does not exist in Company object so I decided to map it later
var result = companies.Select(c => Map(c));
// Here I wanted to return all previously mapped objects + I would like to add to each object HasChildren property, but obliviously my syntax is not good:
return result.Select(c => new { c, c.HasChildrens = companies.Any(cc => cc.ParentCompanyId == c.Id) });
}
I'm retrieving error: Invalid anonymous type declarator.
I've tried to add HasChildrens like this also:
return result.Select(c => {c.HasChildrens = companies.Any(cc => cc.ParentCompanyId == c.Id)});
But still issues..
Basically I simply want to add HasChildrens for each my Mapped DTO and return it as it was added in Map method.
Any kind of help would be great!
Thanks
The return type of your method private IEnumerable<CompDTO> Map(IEnumerable<Company> companies) is IEnumerable<CompDTO>
So the issue is that you're returning an anonymous type rather than the expected CompDTO
Change return result.Select(c => new { ... }
to
return result.Select(c => new CompDTO {
Id = ...
Title = ...
ParentCompanyId = ...
HasChildrens = ...
})
EDIT:
The actual question is:
How do I set the property HasChildrens in the CompDTO while converting from db classes to dto classes
I'd say that the most common way to solve that is to pass in the value while converting:
private CompDTO Map(Company company, bool hasChildrens) {
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
HasChildrens = hasChildrens
};
}
You could also iterate the result after the fact and set the HasChildrens like so: (I wouldn't recommend this)
foreach(dto in result) {
dto.HasChildrens = ...
}
You could also insert the logic of obtaining the HasChildrens value inside of the Map method: (I wouldn't recommend this either)
private CompDTO Map(Company company, IEnumerable<Company> companies) {
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
HasChildrens = companies
.Any(c => c.ParentCompanyId == company.Id)
};
}

Conversion fails between nullable double and int

I have a model, FittingProject, which I'm trying to map to another model, FittingsProjectSharepointModel.
Unfortunately FittingProject and FittingsProjectSharepointModel only share values, both property names and types are different.
To ease the mapping process I have created a custom attribute for FittingProject which I use to look up the matching property on FittingsProjectSharepointModel. The problem is that most values fail during conversion, for example going from int to double?.
As you can see by snippet below I have attempt to use the Convert.ChangeType, but it still fails with the same Exception.
public ModelMapper MapModelToFittingsProjectSharepointModel(object model)
{
if (model == null)
{
return null;
}
var propertiesWithCustomAttributes = model
.GetType()
.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(SharepointModelPropertyAttribute), true).Length > 0);
foreach (var prop in propertiesWithCustomAttributes)
{
foreach (var customAttribute in prop.GetCustomAttributes(true))
{
SharepointModelPropertyAttribute sharePointModelPropertyAttribute =
customAttribute as SharepointModelPropertyAttribute;
PropertyDescriptor sharePointModelprop = TypeDescriptor
.GetProperties(typeof(FittingsProjectSharepointModel))
.Find(sharePointModelPropertyAttribute.SharepointModelProperty, false);
try
{
var projectValue = prop.GetValue(model);
var projectValueConverted = Convert.ChangeType(projectValue, sharePointModelprop.PropertyType);
sharePointModelprop.SetValue(FittingsProjectSharepointModel, projectValueConverted);
}
catch (Exception ex)
{
//
}
}
}
return this;
}
ChangeType does not support that:
Convert.ChangeType(1234, typeof(double)) //works
Convert.ChangeType(1234, typeof(double?)) //fails
So you probably need to do your own nullable processing. Simply strip away the nullable property from the destination type using Nullable.GetUnderlyingType. You also need to handle the case, that the value to be converted is null.
You can use AutoMapper. Like this:
Consider the following example models:
public class FittingProject
{
public string Name { get; set; }
public int Size { get; set; }
}
public class FittingsProjectSharepointModel
{
public string Display { get; set; }
public double? Volume { get; set; }
}
Now, you can create a map between the models like this:
AutoMapper.Mapper.CreateMap<FittingProject, FittingsProjectSharepointModel>()
.ForMember(dest => dest.Display, opts => opts.MapFrom(src => src.Name)) //Map Name to Display
.ForMember(dest => dest.Volume, opts => opts.MapFrom(src => src.Size)); //Map Size to Volume
And here is how you can convert:
FittingProject fitting_project = new FittingProject()
{
Name ="project_name",
Size = 16
};
FittingsProjectSharepointModel sharepoint_model =
AutoMapper.Mapper.Map<FittingsProjectSharepointModel>(fitting_project);

AutoMapper Map to different type based on an enum?

I'm starting to implement AutoMapper, first I managed to integrate it with Castle.Windsor, which I'm already using. Now I have a Post entity which I want to map to either a LinkPostModel or an ImagePostModel. Both inherit from PostModel
1) This is what I have so far:
public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
private readonly IPostService postService;
public PostModelFromPostEntityConverter(IPostService postService)
{
if (postService == null)
{
throw new ArgumentNullException("postService");
}
this.postService = postService;
}
public PostModel Convert(ResolutionContext context)
{
Post post = (Post)context.SourceValue;
Link link = post.Link;
if (link.Type == LinkType.Html)
{
return new LinkPostModel
{
Description = link.Description,
PictureUrl = link.Picture,
PostId = post.Id,
PostSlug = postService.GetTitleSlug(post),
Timestamp = post.Created,
Title = link.Title,
UserMessage = post.UserMessage,
UserDisplayName = post.User.DisplayName
};
}
else if (link.Type == LinkType.Image)
{
return new ImagePostModel
{
PictureUrl = link.Picture,
PostId = post.Id,
PostSlug = postService.GetTitleSlug(post),
Timestamp = post.Created,
UserMessage = post.UserMessage,
UserDisplayName = post.User.DisplayName
};
}
return null;
}
}
Obviously the point in implementing AutoMapper is removing repeat code like this, so how am I supposed to map the common stuff, before adding my custom rules (such as the if-clause)
Ideally I'd want this to be something like:
public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
[...]
public PostModel Convert(ResolutionContext context)
{
Post post = (Post)context.SourceValue;
Link link = post.Link;
if (link.Type == LinkType.Html)
{
return Mapper.Map<Post, LinkPostModel>(post);
// and a few ForMember calls?
}
else if (link.Type == LinkType.Image)
{
return Mapper.Map<Post, ImagePostModel>(post);
// and a few ForMember calls?
}
return null;
}
}
2) After this mapping is complete. I have a "parent" mapping, where I need to map an IEnumerable<Post> the following model:
public class PostListModel : IHasOpenGraphMetadata
{
public OpenGraphModel OpenGraph { get; set; } // og:model just describes the latest post
public IList<PostModel> Posts { get; set; }
}
So basically I'd need another TypeConverter (right?), which allows me to map the posts list first, and then create the og:model
I have this, but it feels kind of clunky, I feel it could be better:
public class PostListModelFromPostEntityEnumerableConverter : ITypeConverter<IEnumerable<Post>, PostListModel>
{
public PostListModel Convert(ResolutionContext context)
{
IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;
PostListModel result = new PostListModel
{
Posts = posts.Select(Mapper.Map<Post, PostModel>).ToList()
};
Post first = posts.FirstOrDefault();
result.OpenGraph = Mapper.Map<Post, OpenGraphModel>(first);
return result;
}
}
3) I didn't actually run the code yet, so another question comes to mind, and that is why aren't mappings strongly typed in converters?
IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;
where it could actually be
IEnumerable<Post> posts = context.SourceValue;
Trying to get Necromancer badge.
Nowadays this task can be solved much easier with using ConstructUsing function specifc fields should be filled in the provided action, but all the common fields will go to ForMember execution of the mapping. Collections in this case doesn't requires any additional logic/mapping configurations. Classes that has a property of type collection as well.
cfg.CreateMap<Post, PostModel>()
.ConstructUsing(p =>
{
switch (p.Type)
{
case LinkType.Html: return new LinkPostModel
{
Title = p.Description
// other specific fields
};
case LinkType.Image: return new ImagePostModel
{
// other specific fields
};
}
return null;
})
.ForMember(x => x.PostId, m => m.MapFrom(p => p.Id));
cfg.CreateMap<PostList, PostListModel>();

Categories