Automapper Many To One conversion
How to convert values of many properties from the source object to a single type in destination object?
Can I use in this case Value Resolvers? Or maybe there is better solution?
Documentation
Here is example from documentation - one to one conversion
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Total,
opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.SubTotal));
Mapper.CreateMap<OtherSource, OtherDest>()
.ForMember(dest => dest.OtherTotal,
opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.OtherSubTotal));
public class CustomResolver : ValueResolver<decimal, decimal> {
// logic here
}
Case
I want to transfer two objects into one (many to one conversion). For example:
public class Document
{
public int CurrencyId {get; set;}
public int ExchangeRateId {get; set;}
}
public class DocumentDto
{
public Currency Currency {get; set;}
}
public class CurrencyDetails
{
public Currency Currency {get; private set;}
public ExchangeRate ExchangeRate {get; private set;}
public CurrencyDetails(Currency currency, ExchangeRate exchangeRate)
{
Currency = currency;
ExchangeRate = exchangeRate;
}
}
I would like to achieve something like that:
public class CurrencyResolver : ValueResolver<int, int, CurrencyDetails>
{
protected override Currency ResolveCore(int currencyId, int exchangeRateId)
{
var currency = new Currency(currencyId); //simplified logic
var exchangeRate = new ExchangeRate(exchangeRateId);
var currencyDetails = new CurrencyDetails(currency, exchangeRate);
return currencyDetails;
}
}
I know that I can pass the whole object as the source object, but for me it is not a solution:
ValueResolver<Document, Currency>
I can't use full object, because I have many document types and i don't want to create new resolver for each document.
Ignoring the element (for manual conversion) is also not allowed in my case. Currency conversion logic must be conducted by AutoMapper.
It's important for me that the conversion took place in background (during the conversion of the main subject).
For example:
Document document;
var documentDto = Mapper.Map<DocumentDto>(document); // and in this moment i have proper CurrencyDetails object!
Thank you for your advice.
My solutions
I figured two solutions, but I dont like them (soooo dirty)
Solution 1 - wrap a class with interface:
public interface ICurrencyHolder
{
int CurrencyId {get; set;}
int ExchangeRateId {get; set;}
}
public class Document : ICurrencyHolder
{
public int CurrencyId {get; set;}
public int ExchangeRateId {get; set;}
}
and use resolver with following parameters:
ValueResolver<ICurrencyHolder, Currency>
Solution 2 - take as source element object type and take values via reflection
ValueResolver<object, Currency>
This is terrible!
If I understand correctly, you need to do the following mapping: from (CurrencyId, ExchangeRateId) to Currency. You can achieve it using Tuple (it is a standard .Net class very handy in these cases):
Mapper.CreateMap<Tuple<int,int>, Currency>()
.ForMember(x => x.Currency, cfg => cfg.MapFrom(y => new Currency(y.Item1, y.Item2));
Invoke the mapper as follows:
Mapper.Map<Tuple<int,int>, Currency>(Tuple.Create(doc.CurrencyId, doc.ExchangeRateId));
Maybee you can map it like this:
Mapper.CreateMap<Source, Destination>()
.ConstructUsing(s => Mapper.Map<Source, Currency>(s));
Mapper.CreateMap<Source, Currency>()
.ForMember(dst => dst.CurrencySymbol, map => map.MapFrom(src => src.DocumentDto.CurrencySymbol))
.ForMember(dst => dst.ExchangeRate , map => map.MapFrom(src => src.Document.ExchangeRate ));
Also possible:
Mapper.CreateMap<Source, Destination>()
.ConstructUsing(s => Mapper.Map<Source, Currency>(s));
Mapper.CreateMap<Source, Currency>()
.ConstructUsing(s => Mapper.Map<DocumentDto, Currency>(s))
.ConstructUsing(s => Mapper.Map<Document, Currency>(s));
Mapper.CreateMap<DocumentDto, Currency>();
Mapper.CreateMap<Document, Currency>();
If you are sure you will be doing this for every document type:
Document document;
var documentDto = Mapper.Map<DocumentDto>(document);
Then you will have to define mappings for each one of them. So I'd definitely go with the ICurrencyHolder idea and use a resolver like this:
The resolver
public class CurrencyResolver : ValueResolver<ICurrencyHolder, Currency>
{
protected override Currency ResolveCore(ICurrencyHolder source)
{
return new Currency(source.CurrencyId, source.ExchangeRateId);
}
}
Document "types"
public class Document : ICurrencyHolder
{
public int CurrencyId { get; set; }
public int ExchangeRateId { get; set; }
}
public class ExtendedDocument : ICurrencyHolder
{
public DateTime SomeDate { get; set; }
public int CurrencyId { get; set; }
public int ExchangeRateId { get; set; }
}
public interface ICurrencyHolder
{
int CurrencyId { get; set; }
int ExchangeRateId { get; set; }
}
And the mappings:
Mapper.CreateMap<Document, DocumentDto>().ForMember(m => m.Currency, opt => opt.ResolveUsing<CurrencyResolver>());
Mapper.CreateMap<ExtendedDocument, DocumentDto>().ForMember(m => m.Currency, opt => opt.ResolveUsing<CurrencyResolver>());
With that in place you can create your dto's like this and get the Currency resolve itself for you at the mapping stage:
var dto = Mapper.Map<DocumentDto>(document);
var extendedDto = Mapper.Map<DocumentDto>(extendedDocument);
Related
I'm kind of new in the world of AutoMapper, just so you know =)
I have 2 classes:
Class LibraryParameters
public class LibraryParameters
{
public int library_id { get; set; }
public string document_name { get; set; } = string.Empty;
public string template_name { get; set; } = string.Empty;
}
Class LibraryDocument
public class LibraryDocument
{
public int libraryId { get; set; }
public string documentName { get; set; } = string.Empty;
public string templateName { get; set; } = string.Empty;
}
So as you can see the variable names are different. So I use AutoMapper for this problem. I configured AutoMapper and made use of .ForMember as you can see below:
CreateMap<LibraryParameters, LibraryDocument>()
.ForMember(dest => dest.libraryId,
opt => opt.MapFrom(src => src.library_id))
.ForMember(dest => dest.templateName,
opt => opt.MapFrom(src => src.template_name))
.ForMember(dest => dest.documentName,
opt => opt.MapFrom(src => src.document_name));
But is it not possible to avoid these different ForMember methods and use ForAllMembers for example? I can't find any information about this anywhere so you guys are my source of help :)
Instead of using .ForAllMembers(), you need to specify the naming convention for the source as below:
For MapperConfiguration
MapperConfiguration _config = new MapperConfiguration(cfg => {
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
});
For Mapping Profile
public class YourProfile : Profile
{
public YourProfile()
{
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
}
}
Demo # .NET Fiddle
References
Naming Conventions | AutoMapper
The models I'm working with include an entry object which I'd like to map as if its child object were the entire object.
Here is a simplified version of the problem. I'd like an instance of OurWrappedSource to map directly to OurTarget.
class OurTarget
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurSource
{
public Guid Id { get; set; }
public string Notes { get; set; }
public int Quantity { get; set; }
}
class OurWrappedSource
{
public OurSource Source { get; set; }
}
private static void TestUnwrapUsingConfig(MapperConfiguration config)
{
config.AssertConfigurationIsValid();
IMapper mapper = new Mapper(config);
var wrappedSource = new OurWrappedSource
{
Source = new OurSource
{
Id = new Guid("123e4567-e89b-12d3-a456-426655440000"),
Notes = "Why?",
Quantity = 27
}
};
var target = mapper.Map<OurTarget>(wrappedSource);
Assert.Equal(wrappedSource.Source.Id, target.Id);
Assert.Equal(wrappedSource.Source.Notes, target.Notes);
Assert.Equal(wrappedSource.Source.Quantity, target.Quantity);
}
The following configuration works, but is unwieldy for more than a couple of members:
// Works, but isn't *auto* enough
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ForMember(src => src.Id, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Id))
.ForMember(src => src.Notes, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Notes))
.ForMember(src => src.Quantity, opts => opts.MapFrom(wrappedSource => wrappedSource.Source.Quantity));
}));
What I'd like to be able to do is define two intermediate mappings an then compose them:
Map OurWrappedSource directly to OurSource
Map OurSource directly to OurTarget
Map OurWrappedSource to OurTarget by composing mapping 1 with mapping 2
After some hammering, I have this configuration:
// Works, but #3 probably isn't ProjectTo-friendly
TestUnwrapUsingConfig(new MapperConfiguration(cfg =>
{
// 1
cfg.CreateMap<OurWrappedSource, OurSource>()
.ConvertUsing(wrappedSource => wrappedSource.Source);
// 2
cfg.CreateMap<OurSource, OurTarget>();
// 3
cfg.CreateMap<OurWrappedSource, OurTarget>()
.ConstructUsing((wrappedSource, ctx) =>
ctx.Mapper.Map<OurTarget>(ctx.Mapper.Map<OurSource>(wrappedSource))
)
.ForAllOtherMembers(opts => opts.Ignore());
}));
This works exactly as specified, but mapping 3 seems perhaps a little more explicit and/or kludgey than it should. It involves code in a Func (rather than an expression), which makes me think it probably won't optimize well when used with ProjectTo(). Is there a way to rewrite mapping 3 to address these issues?
Using Automapper I am trying to map one object to another. One property is a class called Task containing a list of customers. The other class is called Result and contains a count of customers as well as another list of customers.
This is my current approach which fills information into the order properties correctly, but fails in result, which is still null. How can I get the List into result? How do i need to change the maps and do i need to create a map in both directions, or this this not necessary?
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>();
cfg.CreateMap<CustomerPost.Order, Customers.Order>();
cfg.CreateMap<Customers.Result, CustomerPost.Task>();
cfg.CreateMap<CustomerPost.Task, Customers.Result>()
.ForMember(x => x.customerscount, opt => opt.Ignore())
.ForMember(x => x.customerstotalcount, opt => opt.Ignore());
});
try
{
Mapper.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException ex)
{
//TODO: Handle this
throw ex;
}
var customer = Mapper.Map<CustomerPost.RootObject, Customers.RootObject>(input);
here are my current classes (Customer):
public class Result
{
public int customerstotalcount { get; set; }
public int customerscount { get; set; }
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Status status { get; set; }
public Order order { get; set; }
public Result result { get; set; }
}
CustomerPost:
public class Task
{
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Order order { get; set; }
public List<Task> tasks { get; set; }
}
Okay so the solution to my problem was that my mapping didn't find "result" so i've just mapped my RootObject like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>()
.ForMember(x => x.status, opt => opt.Ignore())
.ForMember(x => x.order, opt => opt.Ignore())
.ForMember(dest => dest.result, src => src.MapFrom(opt => opt.tasks.FirstOrDefault()));
then i went ahead and just mapped the result like this:
var result = Mapper.Map<CustomerPost.Task, Customers.Result>(input.tasks.FirstOrDefault());
var customer = new Customers.Customer();
customer = result.customers.FirstOrDefault();
and just bound it to a new Customerobject. Then all my information got transmitted as expected
I am in need to map the below scenario.
public class Customer
{
public string CustomerJson { get; set; }
}
public class CustomerTO
{
public object CustomerJson { get; set; }
}
From DAL I get CustomerJson value as below.
Customer.CustomerJson = {
"name": "Ram",
"city": "India"
}
I am in need to Deserialize this string. so I tried the below stuff while mapping.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustName, opt => opt.MapFrom(src => JsonConvert.DeserializeObject(src.CustName)));
});
But this gives me run time error.
Unhandled Exception: AutoMapper.AutoMapperMappingException: Error mapping types.
So I kept it simple.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustName, opt => opt.MapFrom(src => (src.CustName));
});
And I tried to deserialize it while consuming. But this give compile time error.
var custJson = JsonConvert.DeserializeObject(customerTO.CustomerJson );
Error 2 The best overloaded method match for 'Newtonsoft.Json.JsonConvert.DeserializeObject(string)' has some invalid arguments
I know customerTO.CustomerJson is not string but how do should I do the required mapping?
Thanks.
Based on your previous question and given the information above you seem to be confusing what you're trying to do here.
So I'm going to amalgamate the data from both in an attempt to solve the issues.
Entity Classes:
public class Customer
{
public int CustomerId {get; set; }
public string CustomerName { get; set; }
public string CustomerJson { get; set; }
}
public class CustomerTO
{
public int CustId { get; set; }
public object CustData { get; set; }
public object CustomerJson { get; set; }
}
AppMapper Class:
public static class AppMapper
{
public static MapperConfiguration Mapping()
{
return new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>()
.ForMember(dest => dest.CustId, opt => opt.MapFrom(src => src.CustomerId))
.ForMember(dest => dest.CustData, opt => opt.MapFrom(src => src.CustName))
.ForMember(dest => dest.CustomerJson, opt => opt.MapFrom(src => JsonConvert.DeserializeObject(src.CustomerJson));
});
}
}
Main:
public class Program
{
static void Main(string[] args)
{
var config = AppMapper.Mapping();
var mapper = config.CreateMapper();
// From Previous question get list of Customer objects
var customers = AddCustomers();
var mappedCustomers = mapper.Map<IEnumerable<CustomerTO>>(customers);
}
}
A couple of things to point out
I'm not sure what the purpose of CustData is in CustomerTO. It seems to be duplicating CustomerJson and if so remove it and the associated mapping.
Also, you never mention mapping from DTO back to entity, but for the JsonObject you just need to configure it to map the serialized string to the appropriate Property.
This is how I addressed my requirement.
Db Entity
public class Customer
{
public string CustomerData { get; set; }
// & other properties
}
My DTO
public class CustomerTO
{
public object CustomerData { get; set;}
// & other properties
}
I created a Utility like class with name AppMapper. This is how my AppMapper.cs looks like.
public class AppMapper
{
private IMapper _mapper;
public AppMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerTO>();
//& other such mapping
});
_mapper = config.CreateMapper();
}
public CustomerTO Map(Customer customerEntity)
{
var customerTo= _mapper.Map<Customer,CustomerTO>(customerEntity);
return customerTo;
}
Now when I needed the mapping.
class DAL
{
public CustomerTO GetCustomers()
{
var customers= //LINQ To get customers
var customerTO = Mapping(customer);
return customerTO;
}
//However, this mapping glue in some internal class to retain SOLID principles
private CustomerTO Mapping(Customer custEntity)
{
var custTO = _appMapper.Map(custEntity);
var str = JsonConvert.Serialize(custTO.CustomerData);
custTO.CustomerData = JsonConvert.Deserialize(str);
return custTO;
}
}
That's it.
#Barry O'Kane - Sincere thanks for your inputs.
Points to be noted:-
I don't need to map manually any of the properites since the property name is same. Plus I am casting string to object. So no issues.
If you use .Map() for one property, then I found that I need to map each property else it gives default value of the data type.(Ex. for int it gives 0).
Yes. agreed there could be other method in Automapper which allows me specify that for a particulay property do this manual mapping & for rest use Automapping mechanism. But I am not sure on that.
Please feel free to improve this ans in any way.
Hope this helps :)
I have something like this
public class ProductViewModel
{
public int SelectedProductId { get; set; }
public string ProductName {get; set;}
public int Qty {get; set;}
public List<SelectListItem> Products { get; set};
}
I have a domain like this
public class Product
{
public int ProductId {get; set;}
public string ProductName {get; set;}
public int Qty {get; set;}
}
public class Store
{
public Product() {get; set;}
}
Now I need to do the mapping.
// in my controller
var result = Mapper.Map<ProductViewModel, Store>(Product);
this won't bind anything since it can't figure out how to put the ProductId in since it
is
Store.Product.ProductId;
My map is like this
Mapper.CreateMap<ProductViewModel, Store>().ForMember(dest => dest.Product.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
I get this error
Expression 'dest =>
Convert(dest.Product.SelectedProductId' must
resolve to top-level member. Parameter
name: lambdaExpression
I am unsure how to do this.
To Map nested structures, you just need to create a new object in the MapFrom argument.
Example
Mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.MyNestedType, o => o.MapFrom(t => new NestedType { Id = t.Id }));
Mapper.AssertConfigurationIsValid();
Test Code:
var source = new Source { Id = 5 };
var destination = Mapper.Map<Source, Destination>(source);
Classes:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public NestedType MyNestedType { get; set; }
}
public class NestedType
{
public int Id { get; set; }
}
You can use Resolver.
Create a resolver class like that :
class StoreResolver : ValueResolver<Store, int>
{
protected override int ResolveCore(Store store)
{
return store.Product.ProductId;
}
}
And use it like that :
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.SelectedProductId, opt => opt.ResolveUsing<StoreResolver >());
Hope it will help ...
The error your getting is because you cannot declare mapping declarations more than one level deep in your object graph.
Because you've only posted one property its hard for me to give you the codes that will make this work. One option is to change your viewmodel property to MyTestTestId and the conventions will automatically pick up on that.
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src));
Mapper.CreateMap<ProductviewModel, Product>()
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.