I have a source object that looks like this:
private class SourceObject {
public Enum1 EnumProp1 { get; set; }
public Enum2 EnumProp2 { get; set; }
}
The enums are decorated with a custom [Description] attribute that provides a string representation, and I have an extension method .GetDescription() that returns it. How do I map these enum properties using that extension?
I'm trying to map to an object like this:
private class DestinationObject {
public string Enum1Description { get; set; }
public string Enum2Description { get; set; }
}
I think a custom formatter is my best bet, but I can't figure out how to add the formatter and specify which field to map from at the same time.
Argh, idiot moment. Didn't realize I could combine ForMember() and AddFormatter() like this:
Mapper.CreateMap<SourceObject, DestinationObject>()
.ForMember(x => x.Enum1Desc, opt => opt.MapFrom(x => x.EnumProp1))
.ForMember(x => x.Enum1Desc, opt => opt.AddFormatter<EnumDescriptionFormatter>())
.ForMember(x => x.Enum2Desc, opt => opt.MapFrom(x => x.EnumProp2))
.ForMember(x => x.Enum2Desc, opt => opt.AddFormatter<EnumDescriptionFormatter>());
Problem solved.
Related
Trying to map from Customer to CustomerDto but having issues with that extra layer in the source (I have no control over the source so I cannot align the two unfortunately).
public class Customer
{
public string Name { get; set; }
public AddressSet AddressSet { get; set; }
}
public class AddressSet
{
public AddressSetResults[] AddressSetResults { get; set; }
}
public class AddressSetResults
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class CustomerDto
{
public string Name { get; set; }
public AddressDto AddressDto { get; set; }
}
public class AddressDto
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
The following does not work for the AddressDto, any idea what I'm missing?
CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.AddressDto , opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
Two things:
1) Missing map from AddressSetResults to AddressDto
In order to map inner address you need to create map for these inner types as well:
CreateMap<AddressSetResults, AddressDto>();
2) Map from an element of AddressSetResults array, not from the array itself
This method:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
tells AutoMapper to map to AddressDto from AddressSetResults which is an array of AddressSetResults. This is incorrect, as AutoMapper will not know how to map from array of elements to just one element. Unless you create a map for that too, which would not be a good solution.
Assuming that AddressSetResults will contain up to one address you can fix that adding just one more call FirstOrDefault() to the end of mapping expression:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()))
FirstOrDefault() needs System.Linq namespace.
Why not just First()? If source AddressSetResults array would contain no elements, then mapping would fail resulting in exception as no elements would be found to satisfy the First() method call. Making it resistant to no elements scenario with FirstOrDefault() is more secure solution.
Final, working configuration:
CreateMap<Customer, CustomerDto>()
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()));
CreateMap<AddressSetResults, AddressDto>();
Development environment:
AutoMapper:7.0.1
NetCore:2.1
Wrong content:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
My Model:
public partial class XsOrdersitems
{
public int Id { get; set; }
public string ParentNo { get; set; }
public string GoodsSn { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
}
My DTO type:
public class DTOOrderItem
{
public string OrderPaNo { get; set; }
public string OrderNo { get; set; }
public string OrderNe { get; set; }
public string OrderComm { get; set; }
}
My mapping configuration:
reateMap<DTOOrderItem, XsSalesitems>()
.ForMember(d => d.Id, opt =>opt.Ignore())
.ForMember(d => d.Name, opt => { opt.MapFrom(s => s.OrderNe); })
.ForMember(d => d.GoodsSn, opt => { opt.MapFrom(s => s.OrderNo);})
.ForMember(d => d.ParentNo, opt => { opt.MapFrom(s =>s.OrderPaNo);})
.ForMember(d => d.Comment, opt => { opt.MapFrom(s => s.OrderComm); });
I tried to use opt. Ignore() to ignore the unconfigured mapping attributes, but still reported the above error, please help me, thank you for your time to answer my question.
Per your examples you are mapping to the wrong model. Your CreateMap definition is mapping DTOOrderItem to XsSalesitems, but your comments suggest you are wanting to map to XsOrderitems. Might try adding an additional CreateMap<DTOOrderItem, XsOrderitems>()... with the necessary .Ignore() references and you should be good to go.
Complete mapping definition:
CreateMap<DTOOrderItem, XsOrderitems>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.Name, opt => opt.MapFrom(s => s.OrderNe))
.ForMember(d => d.GoodsSn, opt => opt.MapFrom(s => s.OrderNo))
.ForMember(d => d.ParentNo, opt => opt.MapFrom(s =>s.OrderPaNo))
.ForMember(d => d.Comment, opt => opt.MapFrom(s => s.OrderComm));
This is a reach, but I am going to ask anyway.
I'll lead with my example:
public class PatientInfoModel : IPatientInfoModel, IHaveCustomMappings
{
public string PatientId { get; set; }
public string PatientIdForView { get; set; }
public PatientEpisodeData PatientEpisode { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<PatientInfoRawDto, PatientInfoModel>()
.ForMember(m => m.PatientIdForView, opt => opt.ResolveUsing<PatientIdResolver<PatientInfoRawDto, PatientInfoModel>>())
.ForMember(m => m.PatientId, opt => opt.MapFrom(p => p.patID))
.ForMember(m => m.PatientEpisode, opt => opt.MapFrom(p => new PatientEpisodeData
{
PatientId = p.patID,
PatientIdForView = this.PatientIdForView
}));
}
public class PatientEpisodeData
{
public int PatientId { get; set; }
public string PatientIdForView { get; set; }
}
}
As you can see, with the member PatientEpisode, I would like to map from one of the properties which has already been resolved (PatientIdForView).
As I could not figure out how to do this, I just set the property after the fact. But it would be interesting to find out if this is possible.
Note: I'm not really interested in using a custom value resolver unless you could pass the PatientIdForView property to it.
Cheers
Custom value resolvers do allow you to pass in the destination member value into it (I assume that's what the PatientIdForView property you mention is, the destination member value). If you need the source member value, you can use a member value resolver:
http://docs.automapper.org/en/stable/Custom-value-resolvers.html
You get the destination member, the source member that you specify, and the source/destination objects. Should be everything you need!
I need to map to properties that are both collections of objects of the same type.
For example:
Destination:
public class MyNewObject
{
Collection TypeCollection1 { get; set; }
Collection TypeCollection2 { get; set; }
}
public class MyType
{
string Field1 { get; set; }
string Field2 { get; set; }
}
Source:
public class MyLegacyObject
{
Collection LegacyCollection1 { get; set; }
Collection LegacyCollection2 { get; set; }
}
public class MyLegacyType
{
string OldField1 { get; set; }
string OldField2 { get; set; }
}
For non-collections, I'm used to doing something like this:
Mapper.CreateMap()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(
src => new Collection
{
// is there some kind of ForEach thing I can do???
Field1 = src.OldField1,
Field2 = src.OldField2
??? // this obviously doesn't work because these are the properties on MyType, not the collection
}));
Mapper.CreateMap<MyLegacyType, MyType>()
.ForMember(dest => dest.Field1, opt => opt.MapFrom(src => src.OldField1))
.ForMember(dest => dest.Field2, opt => opt.MapFrom(src => src.OldField2));
Mapper.CreateMap<MyLegacyObject, MyNewObject>()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(src => src.LegacyCollection1))
.ForMember(dest => dest.TypeCollection2, opt => opt.MapFrom(src => src.LegacyCollection2))
I think I solved my own issue. This should do it.
We want to generically map from one database's context.tableType to another
e.g. UatDb.Branch--LiveDb.Branch
The table's are identical so no MapFrom is necessary.
The following generic mapping definition is sufficient
Mapper.CreateMap<TFromContextTableType,TToContextTableType>();
However!!!
We need to wrap the source context.tableType in the following wrapper class:
public class SWrapper<TFrom> where TFrom : class
{
public SWrapper(TFrom model)
{
Model = model;
}
public TFrom Model { get; private set; }
}
Now to perform the mapping we have to map as follows:
Mapper.CreateMap<SWrapper<FromBranchType>, ToBranchType>().ConstructUsing(x => new Live.Branch()))
.ForMember(d => d.BranchID, o => o.MapFrom(x => x.Model.BranchID))
.ForMember(d => d.BranchName, o => o.MapFrom(x => x.Model.BranchName))
.ForMember(d => d.BranchCountry, o => o.MapFrom(x => x.Model.BranchCountry))
This means that we cannot generically map and have to explicitly declare a ForMember for each mapping. I can't figure out any solution using Resolvers or Type Converters.
I thought about perhaps wrapping the target in a SWrapper then resolving the returned Wrapped object to return the internal Product but not sure how to perform this.
All ideas welcome....
Taking from your mapping you could do the following which does not require you to enter all of the property mappings by hand. The helper method in the second test allows for specialized mapping that you may not need.
[TestMethod]
public void Given_a_wrapper_class_I_should_be_able_to_unwrap_it_and_continue_with_mappings()
{
Mapper.CreateMap<Wrapper<Source>, Destination>()
.UnwrapUsing<Wrapper<Source>, Source, Destination>(w => w.Model)
.ForMember(d => d.Bar, o => o.MapFrom(d => d.Baz));
var source = new Source { Baz = 1, Foo = "One" };
var wrapped = new Wrapper<Source> { Model = source };
var destination = Mapper.Map<Destination>(wrapped);
Assert.IsNotNull(destination);
Assert.AreEqual(1, destination.Bar);
Assert.AreEqual("One", destination.Foo);
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDest> UnwrapUsing<TWrapper, TSource, TDest>(this IMappingExpression<TWrapper, TDest> expr, Func<Wrapper<TSource>, TSource> unwrapper)
{
Mapper.CreateMap<Wrapper<TSource>, TDest>()
.ConstructUsing(x => Mapper.Map<TSource, TDest>(unwrapper(x)));
return Mapper.CreateMap<TSource, TDest>();
}
}
public class Source
{
public string Foo { get; set; }
public int Baz { get; set; }
}
public class Destination
{
public string Foo { get; set; }
public int Bar { get; set; }
}
public class Wrapper<T>
{
public T Model { get; set; }
}
How about adding a little utility method to your wrapper class:
public TDest MapTo<TDest>()
{
return Mapper.Map<TFrom, TDest>(this.Model);
}