Automapper cant fill Child Objects (v.6.1.1) - automapper

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

Related

Can I specify a default mapping source for unmapped members with 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);

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 How to change mapping dynamically

I'm connecting to some WebService and receiving Dictionary
that contains keys: "firstName", "lastName", "otherfirstName", "otherLastName".
I have object Person
public class Person
{
public string FirstName{get;set;}
public string LastName{get;set;}
}
Once according to some context I should create my object Person by mapping values from "firstName" and "lastName" and in another case by mapping from "otherfirstName" and "otherlastName"
If I can create only one mapping function how can I map to other person and to change between them dynamically?
Mapper.CreateMap<Response, Person>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Dict["firstName"] : "") ))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Dict["lastName"] : "") ));
I saw some use in Profiles, but I'm not sure how to use it in my case.
Edited
public class FirstMapper : Profile
{
protected override void Configure()
{
base.Configure();
Mapper.CreateMap<Src, Person>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Dct["firstName"]))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Dct["lastName"]));
}
}
public class SecondMapper : Profile
{
protected override void Configure()
{
base.Configure();
Mapper.CreateMap<Src, Person>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Dct["otherfirstName"]))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Dct["otherlastName"]));
}
}
public class Src
{
public Dictionary<string, string> Dct { get; set; }
}
My Test class
public class Test
{
public Test()
{
Src src = new Src();
src.Dct = new Dictionary<string, string>();
src.Dct["firstName"] = "La";
src.Dct["lastName"] = "Nu";
src.Dct["otherfirstName"] = "Fe";
src.Dct["otherlastName"] = "oooo";
Person person = new Person();
ConfigurationStore store = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
store.AssertConfigurationIsValid();
MappingEngine engine = new MappingEngine(store) ;
//add mappings via Profiles or CreateMap
store.AddProfile(new FirstMapper());
person = engine.Map<Person>(src);
}
I have exception on the last line: ""Missing type map configuration or unsupported mapping......."

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.

Why am I getting a "Nullable object must have a value" exception when I'm using Condition?

I'm getting a "Nullable object must have a value" in the example code below. Why do I need the following fix to make this work:
.ForMember(dest => dest.ShirtColor,
dest => dest.MapFrom(src => src.ShirtColor != null
? new OptionSetValue((int) src.ShirtColor)
: null))
AutoMapper.Mapper.CreateMap<PersonA, PersonB>()
.ForMember(dest => dest.FirstName, dest => dest.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastName, dest => dest.MapFrom(src => src.LastName))
// Condition to avoid overwriting existing data!!!
.ForMember(dest => dest.ShirtColor,
dest => dest.Condition(src => src.ShirtColor != null))
.ForMember(dest => dest.ShirtColor,
dest => dest.MapFrom(
src => new OptionSetValue((int)src.ShirtColor)))
// Fix that should not be needed due to condition:
//.ForMember(dest => dest.ShirtColor,
// dest => dest.MapFrom(
// src => src.ShirtColor != null
// ? new OptionSetValue((int) src.ShirtColor)
// : null));
PersonA source = new PersonA();
source.FirstName = "Thomas";
source.LastName = "Jefferson";
source.ShirtColor = null; // nullable int
PersonB destination = new PersonB();
destination.FirstName = "Thomas";
destination.LastName = "Jefferson";
destination.ShirtColor = new OptionSetValue(4);
// Results in: "Nullable object must have a value" despite the fact that
// condition should have been met!
Mapper.Map<PersonA, PersonB>(source, destination);
Debug.Assert(destination.ShirtColor != null);
Console.WriteLine("Our existing data was not overwritten!!");
Console.WriteLine("Hit enter to exit");
Console.ReadLine();
Here is how OptionSet is defined:
public class OptionSetValue
{
public OptionSetValue(){}
public OptionSetValue(int value)
{
Number = value;
}
public int Number { get; set; }
}
Just use PreCondition instead of Condition..
So change your
//Condition to avoid overwriting existing data!!!
.ForMember(dest => dest.ShirtColor, dest => dest.Condition(src => src.ShirtColor != null))
.ForMember(dest => dest.ShirtColor, dest => dest.MapFrom(src => new OptionSetValue((int)src.ShirtColor)))
to
//Condition to avoid overwriting existing data!!!
.ForMember(dest => dest.ShirtColor, dest => dest.PreCondition(src => src.ShirtColor != null))
.ForMember(dest => dest.ShirtColor, dest => dest.MapFrom(src => new OptionSetValue((int)src.ShirtColor)))
You need to change
.ForMember(dest => dest.ShirtColor,
dest => dest.Condition(src => src.ShirtColor != null))
to
.ForMember(dest => dest.ShirtColor,
opt => opt.Condition(src => !src.IsSourceValueNull))
Note that you also don't need the following as they are mapped by convention:
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName))
UPDATE - 2
The problem is related to the fact that the condition and mapping haven't been combined. I've created a simple extension method to allow for it.
Here is a test to demonstrate it
Classes
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class PersonA : Person
{
public int? ShirtColor { get; set; }
}
public class PersonB : Person
{
public OptionSetValue ShirtColor { get; set; }
}
public class OptionSetValue
{
public int OptionSet { get; set; }
}
MappingConfiguration
public class MyProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<PersonA, PersonB>()
.ForMember(dest => dest.ShirtColor,
opt => opt.ConditionallyMapFrom(
src => new OptionSetValue
{
OptionSet = src.ShirtColor.Value
},
src => src.ShirtColor.HasValue));
}
}
public static class AutoMapperExtensions
{
public static void ConditionallyMapFrom<TSource, TMember>(
this IMemberConfigurationExpression<TSource> expression,
Expression<Func<TSource, TMember>> sourceMember,
Func<TSource, bool> condition)
{
expression.Condition(condition);
expression.MapFrom(sourceMember);
}
}
UnitTests
[TestFixture]
public class MappingTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
}
[Test]
public void AutoMapper_ClientMapping_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
var source = new PersonA
{
FirstName = "FirstA",
LastName = "LastA",
ShirtColor = null
};
var destination = new PersonB
{
FirstName = "FirstB",
LastName = "LastB"
};
destination = Mapper.Map(source, destination);
Assert.That(destination, Is.Not.Null);
Assert.That(destination.FirstName, Is.EqualTo("FirstA"));
Assert.That(destination.LastName, Is.EqualTo("LastA"));
Assert.That(destination.ShirtColor, Is.Null);
}
}
Just cast it to a nullable int:
.ForMember((destination) => destination.ShirtColor,
(memberConfiguration) => memberConfiguration.MapFrom((source) => (int?)source.ShirtColor))
Might help someone.
I was getting this exception when there was a code trying to resolve an enumeration for a invalid value inside automapper.
I also feel the possible scenario for others could be you are creating an exception based on your logic inside automapper.
Mapper.CreateMap<Data.Employee, Domain.Entities.Employee>().
.ForMember(dest => dest.EmployeeType, opt => opt.MapFrom(src =>(EmployeeTypeEnum)src.EmployeeTypeId))
// if the EmployeeTypeId has an invalid value which does not match the enum values we get exception

Resources