Automapper - Map custom object nested inside List of object - c#

I need to map List<Source> to List<Dest>, The issue is that Source contains NestedObject inside it & Dest also contains a NestedObject inside it. I need to map these two also while my List is being mapped. I have tried everything but either the nested object always remains null and doesnt get mapped or I get the following exception:
Missing type map configuration or unsupported mapping. Mapping types:
.....
Structure of Source & Destinations:
Source:
public class Source {
public string Prop1 { get; set; }
public CustomObjectSource NestedProp{ get; set; }
}
public class CustomObjectSource {
public string Key { get; set; }
public string Value{ get; set; }
}
Destination:
public class Destination {
public string Prop1 { get; set; }
public CustomObjectDest NestedProp{ get; set; }
}
public class CustomObjectDest {
public string Key { get; set; }
public string Value{ get; set; }
}
What I have tried:
I have the following code, tried several other approaches also but to no avail:
var config = new MapperConfiguration(c =>
{
c.CreateMap<Source, Destination>()
.AfterMap((Src, Dest) => Dest.NestedProp = new Dest.NestedProp
{
Key = Src.NestedProp.Key,
Value = Src.NestedProp.Value
});
});
var mapper = config.CreateMapper();
var destinations = mapper.Map<List<Source>, List<Destination>>(MySourceList.ToList());
I am stuck with this for days, Kindly help.

You got to map the CustomObjectSource to CustomObjectDest as well.
This should do it:
var config = new MapperConfiguration(c =>
{
c.CreateMap<CustomObjectSource, CustomObjectDest>();
c.CreateMap<Source, Destination>()
.AfterMap((Src, Dest) => Dest.NestedProp = new CustomObjectDest
{
Key = Src.NestedProp.Key,
Value = Src.NestedProp.Value
});
});
var mapper = config.CreateMapper();
var MySourceList = new List<Source>
{
new Source
{
Prop1 = "prop1",
NestedProp = new CustomObjectSource()
{
Key = "key",
Value = "val"
}
}
};
var destinations = mapper.Map<List<Source>, List<Destination>>(MySourceList.ToList());

Related

Mapster and nonmatching member names

When field names don't match, Mapster won't map. Fields are set to nulls or zeroes depending on type.
How do I use custom names for target attributes?
Ex.: How do I change SchoolClassTeacherName to just Name? Changing it in the code below and in class SchoolDTO gives me null as a result of mapping.
I've read the documentation but there is no answer. Please, help me. Thank you.
config.NewConfig<SchoolPoco, SchoolDTO>()
.Map(dest => dest.SchoolClassTeacherName,
src => src.School.Class.Teacher.Name)
I have this in my Startup.cs
var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
typeAdapterConfig.Scan(Assembly.GetExecutingAssembly());
var mapperConfig = new Mapper(typeAdapterConfig);
services.AddSingleton<IMapper>(mapperConfig);
I have mapping in a class
public class ProjectMappingProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// Mappings here
}
}
Use TypeAdapterConfig as described in documentation.
Make sure that School, Class, Teacher, Name properties is set.
Sample code:
using Mapster;
TypeAdapterConfig<DtoA, DtoB>
.NewConfig()
.Map(dest => dest.Main_Property, src => src.MainProperty)
.Map(dest => dest.Inner_Property, src => src.InnerDto.InnerProperty);
DtoA a = new DtoA()
{
MainProperty = "Hello",
InnerDto = new InnerDto() { InnerProperty = "World" }
};
DtoB b = a.Adapt<DtoB>();
class DtoA
{
public string MainProperty { get; set; }
public InnerDto InnerDto { get; set; }
}
class InnerDto
{
public string InnerProperty { get; set; }
}
class DtoB
{
public string Main_Property { get; set; }
public string Inner_Property { get; set; }
}
Result in b variable:

Q:AutoMapper unable to use TypeCode in Destination

I got a very strange problem,when i add a property named [IdTypeCode] with decimal type in
Destination,the Map method will throw an exception. Does anyone know how to solve it? Thanks.
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
public decimal IdTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(c => c.CreateMap<Source, Destination>());
var mapper = configuration.CreateMapper();
//will throw an exception
var dest = mapper.Map<Destination>(new Source
{
Id = 1
});
}
}
I believe this is a side-effect of AutoMapper attempting to "flatten" the source Id property into the destination object.
Since your destination has the property IdTypeCode, AutoMapper will try to find a matching value from the source. And in this case, it seems to be picking up on the Type.GetTypeCode() method on the Source.Id property, and hence trying to map a System.TypeCode to a decimal. This can be seen in the exception that's thrown:
Destination Member: IdTypeCode
---> AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
TypeCode -> Decimal
System.TypeCode -> System.Decimal
This can be verified by changing the type of Destination.IdTypeCode to System.TypeCode:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
public TypeCode IdTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(c => c.CreateMap<Source, Destination>());
var mapper = configuration.CreateMapper();
//will throw an exception
var dest = mapper.Map<Destination>(new Source
{
Id = 1
});
Console.WriteLine(dest.IdTypeCode);
}
}
which causes the mapping to succeed and the Destination.IdTypeCode getting mapped with the value Int32.
With that said, aside from changing the name of the property, one easy solution is just to ignore that property on the destination:
var configuration = new MapperConfiguration(c =>
{
c.CreateMap<Source, Destination>()
.ForMember(d => d.IdTypeCode, opt => opt.Ignore());
});
according to Lucian's answer, i change my code as below,it works well
public class Source
{
public string Card { get; set; }
}
public class Destination
{
public string Card { get; set; }
public decimal CardTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(configure =>
{
//set this func
configure.ShouldMapMethod = _ => false;
configure.CreateMap<Source, Destination>();
});
var mapper = configuration.CreateMapper();
var dest = mapper.Map<Destination>(new Source
{
Card = "1"
});
}
}

Automapper along with generics and mapping missing properties

I'm trying to use a generic mapper for mapping two objects. So I have setup Automapper in this way which comes from the documentation:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
});
mapper = config.CreateMapper();
Now everything works well in the case if the source and destination have the same properties and if I'm going from more properties on the source side to less properties on the destination. However if the source has less properties than the destination than I get an error:
Value ---> AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
My question is there a way I could ignore these properties even though I won't know what they are at compile time?
Source:
public class Source<T>
{
public T Value { get; set; }
}
public class Destination<T>
{
public T Value { get; set; }
}
public class DataObject1
{
public int Id { get; set; }
public string Code { get; set; }
}
public class DataObject2
{
public int Id { get; set; }
public string Code { get; set; }
public string ActiveFlag { get; set; }
}
Here is my Test Code:
var data = new DataObject1() { Id = 10, Code = "Test" };
var source = new Source<DataObject1> { Value = data };
var dest = mapper.Map<Source<DataObject1>, Destination<DataObject2>>(source);
Assert.AreEqual(dest.Value.Id, 10);
Your mapping will succeed if you map DataObject1 to DataObject2 when you configure your mapper like so:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
cfg.CreateMap<DataObject1, DataObject2>();
});
... or are you trying to avoid having to know at compile time that you may need to map DataObject1 to DataObject2 at all?

How can I flatten a nested class when using an instance of (not static) AutoMapper?

I'm trying to use AutoMapper (v6.1.1) to flatten out a class containing further nested classes.
For reasons™ I'm unable to alter these classes, so altering names is not possible.
Bearing this in mind I can solve it using the static Mapper like so:
// This is the nested user, it has a further nested class
public class NestedUser
{
public long id { get; set; }
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class Attributes
{
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// This is the flattened representation
public class FlattenedUser
{
public long id { get; set; }
public string type { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// Create a nested user
var nested = new NestedUser
{
id = 1,
type = "Contact",
attributes = new Attributes
{
first_name = "Equals",
last_name = "Kay",
name = "Equalsk"
}
};
// Use the static Mapper to flatten
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Mapper.AssertConfigurationIsValid();
var flattened = Mapper.Map<FlattenedUser>(nested);
The object flattened now has all of its properties correctly populated.
For more reasons™ I want to use an instance of AutoMapper, something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
config.AssertConfigurationIsValid();
var flattened = config.CreateMapper().Map<FlattenedUser>(nested);
My issue is the line .ConstructUsing(s => ... ));, it refers to the static Mapper and so it throws a runtime exception:
System.InvalidOperationException: 'Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.'
I don't want to use .ForMember(...) for each nested property as it defeats the object of what I'm trying to do.
Now I'm stuck. Is it possible to flatten out the nested class using an instance of AutoMapper as opposed to the static way?
I see 2 options to resolve the issue, depending on your case one should fit:
First: if you want to continue using static mapper for nested object than you have to move registration of nested objects to static configuration:
Mapper.Initialize(cfg=> cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None));
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Second: Use ResolutionContext
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing((s, r) => r.Mapper.Map<FlattenedUser>(s.attributes));
});

AutoMapper with prefix

I'm trying to use Automapper to map to objects, the issue is one of the objects I'm trying to map has a prefix 'Cust_' in front of all its properties and one doesn't. Is there a way to make this mapping.
For example say I have
class A
{
String FirstName { get; set; }
String LastName { get; set; }
}
class B
{
String Cust_FirstName { get; set; }
String Cust_LastName { get; set; }
}
Obviously this map won't work
AutoMapper.Mapper.CreateMap<A, B>();
b = AutoMapper.Mapper.Map<A, B>(a);
Mapper.Initialize(cfg =>
{
cfg.RecognizeDestinationPrefixes("Cust_");
cfg.CreateMap<A, B>();
});
A a = new A() {FirstName = "Cliff", LastName = "Mayson"};
B b = Mapper.Map<A, B>(a);
//b.Cust_FirstName is "Cliff"
//b.Cust_LastName is "Mayson"
Or alternatively:
Mapper.Configuration.RecognizeDestinationPrefixes("Cust_");
Mapper.CreateMap<A, B>();
...
B b = Mapper.Map<A, B>(a);
...
The documentation has an article on Recognizing pre/postfixes
Sometimes your source/destination properties will have common pre/postfixes that cause you to have to do a bunch of custom member mappings because the names don't match up. To address this, you can recognize pre/postfixes:
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; }
}
Mapper.Initialize(cfg => {
cfg.RecognizePrefix("frm");
cfg.CreateMap<Source, Dest>();
});
Mapper.AssertConfigurationIsValid();
By default AutoMapper recognizes the prefix "Get", if you need to clear the prefix:
Mapper.Initialize(cfg => {
cfg.ClearPrefixes();
cfg.RecognizePrefixes("tmp");
});

Categories

Resources