Can I specify a default mapping source for unmapped members with AutoMapper? - automapper

I have a type that encapsulates a bunch of related data
class CollectionNoteDetails
{
CollectionNote Note {get;set;}
Customer SourceCustomer {get;set;}
Customer DestinationCustomer {get;set;}
Haulier Haulier{ get;set;}
}
and I want to flatten it
class CollectionNoteDto
{
// using fields for brevity
int Id;
DateTime CollectionDate;
// ... plus about 30 more props
string SourceCustomerName;
string DestinationCustomerName;
string HaulierName;
}
there's about 30 properties on the DTO that come off the CollectionNote, and a few that come off the other entities. The other entities are easy to handle in the CreateMap() call:
CreateMap<CollectionNoteDetails, CollectionNoteDoc>(MemberList.Destination)
.ForMember(d => d.SourceCustomerName, o => o.MapFrom(s => s.SourceCustomer.Name))
.ForMember(d => d.DestinationCustomerName, o => o.MapFrom(s => s.DestinationCustomer.Name))
.ForMember(d => d.HaulierName, o => o.MapFrom(s => s.Haulier.Name));
... but is there an easy way to map the rest of the properties to the Note source property? Something like this made up method.
CreateMap<CollectionNoteDetails, CollectionNoteDoc>(MemberList.Destination)
.MapAllByDefault(o => o.MapFrom(s => s.Note))
// other explicit mappings here
.ForMember(d => d.HaulierName, o => o.MapFrom(s => s.Haulier.Name));

You might need to recognize a prefix. The example from the documentation:
public class Source {
public int frmValue { get; set; }
public int frmValue2 { get; set; }
}
public class Dest {
public int Value { get; set; }
public int Value2 { get; set; }
}
var configuration = new MapperConfiguration(cfg => {
cfg.RecognizePrefixes("frm");
cfg.CreateMap<Source, Dest>();
});
On different matters, note that flattening is automatically supported by automapper, which means that this code:
CreateMap<CollectionNoteDetails, CollectionNoteDoc>(MemberList.Destination)
.ForMember(d => d.SourceCustomerName, o => o.MapFrom(s => s.SourceCustomer.Name))
.ForMember(d => d.DestinationCustomerName, o => o.MapFrom(s => s.DestinationCustomer.Name))
.ForMember(d => d.HaulierName, o => o.MapFrom(s => s.Haulier.Name));
is equivalent to:
CreateMap<CollectionNoteDetails, CollectionNoteDoc>(MemberList.Destination);

Related

The following error occurred when I was using AutoMapper

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));

AutoMapper -- One-to-many mapping of tree

I have the following classes:
public class OneToManySource {
public OneToManySource SourceChild { get;set; } // will sometimes be null
public int Value { get; set; }
}
public interface IDestination
{
}
public class ChildlessDestination: IDestination
{
public int DestValue { get; set; }
}
public class ChildedDestination: IDestination
{
public int DestValue { get; set; }
public IDestination DestinationChild { get; set; } // Never null. If it would be null, use a ChildlessDestination instead.
}
I want to map these back and forth in the sensible way. If a source has a child, it goes to a ChildedDestination. Otherwise, it goes to a ChildlessDestination.
I have the following, which works, but it's ugly. I'm wondering if it can be cleaned up. In particular, the "ConstructUsing" gets the job done, but it also seems to be (understandably) opaque to AutoMapper's internal smarts. So ReverseMap() there doesn't work. Instead of that, we are stuck with two other ReverseMap() calls, and also the last CreateMap().
For example, would I be better off starting with the reverse map?
public MapperConfigurationExpression Configure(MapperConfigurationExpression expression) {
expression.CreateMap<OneToManySource, ChildedDestination>()
.ForMember(d => d.DestValue, cfg => cfg.MapFrom(s => s.Value))
.ForMember(d => d.DestinationChild, opt => opt.MapFrom(s => s.SourceChild))
.ReverseMap();
expression.CreateMap<OneToManySource, ChildlessDestination>()
.ForMember(d => d.DestValue, cfg => cfg.MapFrom(s => s.Value))
.ReverseMap();
expression.CreateMap<OneToManySource, IDestination>()
.ConstructUsing((source, context) =>
{
if (source.SourceChild == null) {
return context.Mapper.Map<ChildlessDestination>(source);
}
return context.Mapper.Map<ChildedDestination>(source);
});
expression.CreateMap<IDestination, OneToManySource>()
.ConstructUsing((source, context) =>
{
return context.Mapper.Map<OneToManySource>(source);
});
return expression;
}

Automapper cant fill Child Objects (v.6.1.1)

I have this main class with 2 complex nested objects
public class OrderData
{
public OrderDO OrderDO { get; set; }
public CustomerDO CustomerDO { get; set;
}
My OrderDO Class
public class OrderDO
{
public OrderDO()
{
OrderItemDOList = new List<OrderItemDO>();
PaymentLogDOList = new List<PaymentLogDO>();
}
public int Id { get; set; }
...
}
}
And CustomerDO Class
public class CustomerDO
{
public CustomerDO()
{
OrderDOList = new List<OrderDO>();
}
public int Id { get; set; }
...
}
Last OrderItemDO Class
public class OrderItemDO
{
public int Id { get; set; }
...
}
I cant make mapping OrderItemDOList and PaymentLogDOList under OrderDO :
OrderDO orderDO = Mapper.Map<OrderMain, OrderDO>(orderMain);
OrderItemDOList and PaymentLogDOList are null.
As mapping between complex objects is a simple thing which AutoMapper definitely does well, this leaves the likely cause of the problem as one of the following:
The source objects are not populated (lazy loading?)
You have not defined mappings between the inner objects
You can check the first item with a breakpoint on your call to Mapper.Map.
While you've not included enough code to determine if it's the second issue, you can most likely find this yourself by testing your mappings: simply run Mapper.AssertConfigurationIsValid(). If AutoMapper doesn't know how to map something you've got, it'll tell you what the problem is in the exception.
I find this way. Child Mapping types will be declared with ForMember()
method
Like This :
config.CreateMap<OrderMain, OrderDO>()
.ForMember(dest => dest.OrderItemDOList, opt => opt.ResolveUsing(fa => fa.OrderItem))
.ForMember(dest => dest.PaymentLogDOList, opt => opt.ResolveUsing(fa => fa.PaymentLog));
config.CreateMap<OrderDO, OrderMain>()
.ForMember(dest => dest.OrderItem, opt => opt.ResolveUsing(fa => fa.OrderItemDOList))
.ForMember(dest => dest.PaymentLog, opt => opt.ResolveUsing(fa => fa.PaymentLogDOList));
config.CreateMap<OrderItemDO, OrderItem>()
.ForMember(dest => dest.Id, opt => opt.ResolveUsing(fa => fa.Id))
.ForMember(dest => dest.DisplayName, opt => opt.ResolveUsing(fa => fa.DisplayName));
config.CreateMap<OrderItem, OrderItemDO>()
.ForMember(dest => dest.Id, opt => opt.ResolveUsing(fa => fa.Id))
.ForMember(dest => dest.DisplayName, opt => opt.ResolveUsing(fa => fa.DisplayName));
config.CreateMap<PaymentLog, PaymentLogDO>()
.ForMember(dest => dest.Id, opt => opt.ResolveUsing(fa => fa.Id))
.ForMember(dest => dest.OrderId, opt => opt.ResolveUsing(fa => fa.OrderId));

map several objects to one object using automapper

Is it possible to map one object to several objects using automapper? I know that it is possible to do it the other way around as shown here.
One way to do it:
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
Full example:
public class Source1
{
public int NumValue { get; set; }
public string StringValue { get; set; }
}
public class MAINSource
{
public Source1 Inner1 { get; set; }
public Source1 Inner2 { get; set; }
}
public class Dest1
{
public int NumValue { get; set; }
}
public class Dest2
{
public string StringValue { get; set; }
}
public class MAINDest
{
public Dest1 Inner1 { get; set; }
public Dest2 Inner2 { get; set; }
}
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
var innerSource = new Source1 {NumValue = 1, StringValue = "supervalue"};
var mainSource = new MAINSource
{
Inner1 = innerSource,
Inner2 = innerSource
};
var destination = Mapper.Map<MAINSource, MAINDest>(mainSource);
destination.Inner1.NumValue.ShouldEqual(1);
destination.Inner2.StringValue.ShouldEqual("supervalue");

Using AutoMapper to map multiple collection properties of the same type

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.

Resources