Automapper not mapping from a source containing a single nullable boolean field - c#

I'm seeing some very strange behaviour in Automapper when mapping from a object with a single Nullable<bool> property. I currently have it set up like this:
public class MyViewModel
{
public bool? IsAThing { get; set; }
}
public class MyEntity
{
public bool? IsAThing { get; set; }
public bool? HasAnotherThing { get; set; }
public string AnotherThing { get; set; }
// Lots of other fields
}
with the following mapping profile (with a similar reverse mapping):
CreateMap<MyViewModel, MyEntity>()
.ForMember(x => x.IsAThing, opt => opt.MapFrom(y => y.IsAThing))
.ForAllOtherMembers(opt => opt.Ignore());
However if I try to do the following:
var config = new AutoMapperConfig().Configure();
var mapper = config.CreateMapper();
var source = new MyViewModel { IsAThing = true };
var dest = new MyEntity();
mapper.Map(source, dest);
dest.IsAThing is null. The mapping profile is the only place where MyViewModel is declared as a mapping source. Strangely, if I declare the class
public class AnotherThingViewModel
{
public bool? HasAnotherThing { get; set; }
public string AnotherThing { get; set; }
}
And do the following test:
var source = new AnotherThingViewModel { HasAnotherThing = true };
var dest = new MyEntity();
mapper.Map(source, dest);
dest.HasAnotherThing is true as expected!
Obviously I have no idea what's going on here so has anyone seen anything like this before, or knows of any bugs in Automapper that might cause this?

I figured it out, I had CreateMap<MyViewModel, MyEntity>().ForAllMembers(opt => opt.Ignore()) elsewhere in my mapping configuration!

Related

AutoMapper - Apply ForAllMembers instead of multiple ForMembers

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

Automapper. How to map custom type property of inner class (source) to string and string array?

I want to map source inner classes to string and in other case string array but in both cases they are mapped to null. I want MapNoteFromInnerSourceEntity1 to hold a value of InnerSourceEntity1 Id property and MapValueFromInnerSourceEntity2 to hold values of InnerSourceEntity2 value properties. So far automapper is quite difficult for me to understand.
Code:
internal class Program
{
public class InnerSourceEntity1
{
public string Id { get; set; }
public string Note { get; set; }
}
public class InnerSourceEntity2
{
public string Id { get; set; }
public string Value { get; set; }
}
public class SourceEntity
{
public InnerSourceEntity1 A { get; set; }
public IList<InnerSourceEntity2> B { get; set; }
}
public class DestinationEntity
{
public string MapNoteFromInnerSourceEntity1 { get; set; }
public string[] MapValueFromInnerSourceEntity2 { get; set; }
}
static void Main()
{
var source = new SourceEntity
{
A = new InnerSourceEntity1 { Note = "Note", Id = "Id"},
B = new List<InnerSourceEntity2> { new InnerSourceEntity2 { Id = "Id", Value = "Value" }, new InnerSourceEntity2 { Id = "Id", Value = "Value" } }
};
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceEntity, DestinationEntity>();
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>();
});
var mapper = new Mapper(config);
DestinationEntity destination = mapper.Map<SourceEntity, DestinationEntity>(source);
Console.ReadLine();
}
}
Since the names of properties between source type SourceEntity and destination type DestinationEntity don't match, you'll have to explicitly indicate them, otherwise AutoMapper will not know how to fill the properties:
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
Also, don't map between concrete collection types:
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>(); // <== Don't do that.
Instead, see what the docs say about mapping collections:
(...) it’s not necessary to explicitly configure list types, only their member types. ~ AutoMapper Docs
So, you only need to specify map like this:
cfg.CreateMap<InnerSourceEntity2, string>();
And since we are mapping to string we also need to instruct the AutoMapper from where it can get the string value. So, we'll use ConvertUsing() again:
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
Final configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
});

Can AutoMapper mappings be composed?

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?

how do I map this using Automapper

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

AutoMapper - Nested mapping whilst preserving selected child properties

So i have this;
public class Parent
{
public string SomeProperty { get; set; }
public Child ChildProperty { get; set; }
}
public class Child
{
public string ChildProperty { get; set; }
public string OtherChildProperty { get; set; }
}
public class Flat
{
public string SomeProperty { get; set; }
public string ChildProperty { get; set; }
}
And then I do this;
Flat source = new Flat { SomeProperty = "test", ChildProperty = "test" };
Parent dest = GetParentFromDataContext();
Mapper.Map<Flat,Parent>(source,dest);
Then my expectation is that dest.ChildProperty.OtherChildProperty is still set to whatever it was when it was pulled from the datacontext. However I'm struggling to do this.
If I CreateMap as so, then I get a "must resolve to top-level member" exception;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.Parent.ChildProperty.ChildProperty,
options => options.MapFrom(source => source.ChildProperty))
.ForMember(dest => dest.Parent.ChildProperty.OtherChildProperty,
options => options.Ignore());
However if I do the following, then the new Child {} replaces the Child pulled from the datacontext essentially clearing OtherChildProperty;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.Child
options => options.MapFrom(source => new Child { ChildProperty = source.ChildProperty } ));
How can i map this and preserve the child properties I wish to ignore?
You are trying to reverse flattening process with automapper and that is just wrong tool for the job. See this SO question.
Inelegant, but this works;
Mapper.CreateMap<Flat,Parent>()
.ForMember(dest => dest.ChildProperty, options => options.Ignore());
Mapper.CreateMap<Flat,Child>()
.ForMember(dest => dest.OtherChildProperty, options => options.Ignore());
Mapper.Map<Flat,Parent>(source,dest);
Mapper.Map<Flat,Child>(source,dest.Child);

Categories