AutoMapper.Mapper.CreateMap<TSource,TDestination>()' is obsolete - c#

I have to classes Like
class A
{
public int id {get; set;}
}
class B
{
public C c {get; set;}
}
class C
{
public int id {get; set;}
public string Name {get; set;}
}
My requirement is to map id of class A to id of class C.
Now what I was doing till now was:
Mapper.CreateMap().ForMember(des => des.C.Id, src => src.MapFrom(x => x.id));
and it was working fine.
Now seems like Auto mapper has changed their implementation. and I am getting warning as below:
AutoMapper.Mapper.CreateMap()' is obsolete: 'Dynamically creating maps will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed, or Mapper.Initialize. Use CreateMapper to create a mapper instance.
I need to map some properties of classes which has different name and structure. Any help on this.

Previously
Mapper.CreateMap<Src, Dest>()
.ForMember(d => d.UserName, opt => opt.MapFrom(/* ????? */));
The problem here is mapping definitions are static, defined once and reused throughout the lifetime of the application. Before 3.3, you would need to re-define the mapping on every request, with the hard-coded value. And since the mapping configuration is created in a separate location than our mapping execution, we need some way to introduce a runtime parameter in our configuration, then supply it during execution.
This is accomplished in two parts: the mapping definition where we create a runtime parameter, then at execution time when we supply it. To create the mapping definition with a runtime parameter, we “fake” a closure that includes a named local variable:
Mapper.Initialize(cfg => {
string userName = null;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
});
For more information see this
For one or more classes
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
cfg.CreateMap<AbcEditViewModel, Abc>();
cfg.CreateMap<Abc, AbcEditViewModel>();
});
In mapping class
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);

Another way that seems a bit cleaner is to make a MappingProfile class which inherits from the Profile class of AutoMapper
public class MappingProfile:Profile
{
public MappingProfile()
{
CreateMap<Source1, Destination1>();
CreateMap<Source2, Destination2>();
...
}
}
Then you initialize the mapping with Mapper.Initialize(c => c.AddProfile<MappingProfile>()); in your startup code
That will allow you to use the mapping anywhere by calling
destination1Collection = source1Collection.Select(Mapper.Map<Source1, Destination1>);

Finally I found the resolution. I was doing: Mapper.Initialize{ Mapping field from source to destination }
in the App_start and adding this file to the global.asax--> Application_Start() --> GlobalConfiguration.
I need to add one more line inside my Mapper.Initialize which is cfg.CreateMissingTypeMaps = true;
Now this code will work for explicit mapping where two classes don't have the same structure and names of properties.
Apart from this, if we need to map properties of two class with the same structure the code Mapper.map(source, destination) will also work, which was not working earlier.
Let me know if someone is having difficulty with the solution. Thanks all for the above reply.

Related

Automapper unmapped properties error with simple objects mappings

I need to map a model object coming from API to my actual entity object on DbContext. It is used when creating a new machine object using a POST action.
As always, I created a simple map for the source/destination objects.
In this case we consider the source object as the API model and the destination object as the entity. Also the model has just a subset of properties of the entity.
Source/destination types
// Destination (entity on DbContext)
public class Machine
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string MnmConfiguration { get; set; }
public MachineBootStatus Status { get; set; }
public long MachineDriverId { get; set; }
public MachineDriver MachineDriver { get; set; }
public string DriverConfiguration { get; set; }
public string DriverStatus { get; set; }
}
// Source (from API controller)
public class MachineCreateModel
{
public string Name { get; set; }
public string Description { get; set; }
public string MnmConfiguration { get; set; }
public long MachineDriverId { get; set; }
}
Mapping configuration
public class DomainProfile : Profile
{
public DomainProfile()
{
//CreateMap<MachineCreateModel, Machine>();
// Update 2019/01/30 with proposed solution
CreateMap<MachineCreateModel, Machine>(MemberList.Source);
}
}
I'm using Unity DI container and the configuration of AutoMapper is this:
container = new UnityContainer();
// ... some other configurations...
container.RegisterType<IMapper, Mapper>(new InjectionConstructor(new MapperConfiguration(cfg => cfg.AddProfile<DomainProfile>())));
Version
Using AutoMapper v8.0.0.
Expected behavior
I expect to obtain a simple automatic mapping without errors, since my source model is just a subset of properties of the destination model, with same names.
Actual behavior
I get this error about unmapped properties when I hit this line of code:
Machine entity = Mapper.Map<Machine>(request.Machine);
[14:08:34.363 8 2e62361a INF] Creating new machine: TEST M1
[14:08:36.205 8 bd577466 ERR] An unhandled exception has occurred while executing the request.
AutoMapper.AutoMapperConfigurationException:
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
=================================================================================================
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
MachineCreateModel -> Machine (Destination member list)
MyApplication.Dcs.Application.Models.MachineCreateModel -> MyApplication.Dcs.Domain.Entities.Machine (Destination member list)
Unmapped properties:
Id
Status
MachineDriver
DriverConfiguration
DriverStatus
at AutoMapper.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps)
at lambda_method(Closure , MachineCreateModel , Machine , ResolutionContext )
at lambda_method(Closure , Object , Object , ResolutionContext )
at AutoMapper.Mapper.AutoMapper.IMapper.Map[TDestination](Object source)
at MyApplication.Dcs.Application.Commands.MachineCreateCommandHandler.Handle(MachineCreateCommand request, CancellationToken cancellationToken) ..Commands\MachineCreateCommand.cs:line 28
Note
In my solution I've many projects and 3 of them are making use of AutoMapper (same version for all). There're 3 different DomainProfile.cs files (1 for each project) with the needed mappings.
In the other 2 DomainProfile classes I've some manual mappings (see example below) because I need to "translate" an object with italian property names to another one with english property names. So there're many lines for each object mapping, such as:
CreateMap<ArticleCreateUpdateModel, Articoli>()
.ForMember(d => d.Categoria, opt => opt.MapFrom(src => src.Category))
.ForMember(d => d.CodiceArticolo, opt => opt.MapFrom(src => src.Code))
.ForMember(d => d.Descrizione, opt => opt.MapFrom(src => src.Description))
.ForMember(d => d.Famiglia, opt => opt.MapFrom(src => src.Family))
.ForMember(d => d.Note, opt => opt.MapFrom(src => src.Note))
...
I don't know if the usage of those manual members mapping on one or more DomainProfile class, obliges me in some way to always explain all the subsequent mappings, even if they should be simple like those of this example.
By default, AutoMapper validates the destination properties. As there are neither matching properties nor ForMember constructs for a bunch of properties in your destination type you get the exception above.
Try to validate on the source properties instead:
CreateMap<ArticleCreateUpdateModel, Articoli>(MemberList.Source)
.ForMember(d => d.Categoria, opt => opt.MapFrom(src => src.Category))
// ...
Remark:
On the other hand, I have to mention that this is the typical case when AutoMapper is an overkill. Apart from trivial cases I would never use it anymore.
I had to use it in a project for more than a year but actually it is only good for making simple things more complicated than necessary. Some FromDto and ToDto [extension] methods are just simpler, faster, easier to debug and more reactive to code changes. Mapping between different class layouts or property names often results practically as much code (or even more with tons of lambdas) as simply writing the mapping manually. See also this link.

Is it possible to created a collection on the dest based on a collection in source?

Say I have
public class A
{
public List<int> Ids {get;set;}
}
public class B
{
public List<Category> Categories {get;set;}
}
public class Category
{
public string Name {get;set;} //will be blank on map
public int CategoryId {get;set;}
}
var source = new A {...};
var b = mapper.Map<A, B>(source);
so when mapped it will actually create a new collection on the dest but will map the ids based on what's in the source collection, other properties of the dest will be blank because there is nothing to map from.
How to setup the configuration to do this mapping?
You need a combination of ForMember, MapFrom and ForAllOtherMembers:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<A, B>()
.ForMember(dest => dest.Categories, opt => opt.MapFrom(src => src.Ids));
cfg.CreateMap<int, Category>()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src))
.ForAllOtherMembers(opt => opt.Ignore());
});
MapFrom will allow you to override the default mapping-by-name that AM normally does. As you can see in line 4, we can say that Ids in the source maps to Categories in the destination class.
But now you need to override how an int gets mapped, since that is the type of thing in Ids. With MapFrom, you don't (necessarily) have to provide a property for the source--the entire source itself can be the thing being mapped. So in line 7, we are mapping the ints that came from the mapping in line 4 and saying that they should map to the destination class' CategoryId property. Finally, we simply tell AM that we don't care to map any remaining properties in the target class by specifying the ForAllOtherMembers option.

AutoMapper issue- Automap the DTO,Viewmodel if DTO contains another DTo

Automap the dto with subDto
class productsDTO
{
public int id;
public AddressDTO DeliveryAddress;
}
Class productsViewModel
{
public int id;
public AddressViewModel DeliveryAddress;
}
Here, I have one dto class. I just want to auto map the dTo class into view model. In DTO class has AddressDTo that has to be automatically mapped AddressViewModel in productsViewModel.
If anyone have solution just post
var products = [some objects];
products.ForEach(a =>
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ProductsDTO, ProductsViewModel>()
.ForMember(dest => dest.DeliveryAddress, opts =>opts.Ignore())
.AfterMap((src, dest) => {
dest.DestinationAddress =
Mapper.Map(src.DeliveryAddress,dest.DeliveryAddress);
});
});
IMapper iMapper = config.CreateMapper();
var productList = iMapper.Map<ProductsDTO, ProductsViewModel>(a);
products.add(productList)
});
This code produce an error like this : "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."
You need to add a second mapping, between AddressDTO and AddressViewModel. When when you map between productsDTO and productsViewModel it will automatically map those properties as well, and AutoMapper will know how the mapping should work.
In the same way you mapped productsDTO and productsViewModel, you can map these classes too:
cfg.CreateMap<AddressDTO, AddressViewModel>();
Also, it should not be necessary to define your mappings multiple times inside a loop, as you are doing now. The mapping is simply a definition, to be used later. It only needs to be specified once. When you want to actually carry out a mapping of two objects, then use Mapper.Map - use this as many times as you have objects. It will use the mapping definition you created beforehand. But cfg.CreateMap only needs to be called once for each combination of classes. I'm also not sure you need the whole "Ignore" business in your code - it seems redundant to ignore a property and then map it again on the next line. You can also convert all the objects in the products list in a single Map operation.
Something like this should work better, I would think:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ProductsDTO, ProductsViewModel>();
cfg.CreateMap<AddressDTO, AddressViewModel>();
});
IMapper iMapper = config.CreateMapper();
var products = [some objects];
List<ProductViewModel> productVMList = iMapper.Map<List<ProductViewModel>>(products);
Firstly, You map AddressDTO and AddressViewModel properties.If you give same property name AutoMapper mapped automatically. If you wanna to map properties with different name, You define which property equals with other...
Docs ;
http://docs.automapper.org/en/stable/Projection.html
After You will mapped productsDTO and productsViewModel and Everythings work fine.. :)

How to use Auto Mapper For nested objects

I am facing issue for mapping for nested objects using Auto Mapper method
Can any one give suggestion to map Source and Target class using auto mapper
For example this is sample code.
Source:
Main Class:
public class EMP {
public int empid {get;set;}
public int Name {get;set;}
public EMPDetails Employeedata {get;set;}//Here is inner object in main class
public EmpContact EmpContactdetails {get;set;}//Here is other inner object in main class
}
public Class EMPDetails {
public String Name {get;set;}
public EmpAdd EmpAddress {get;set;}//Here is inner Object in one class
}
public class EmpAdd {
public int id {get;set;}
public int Street {get;set;}
}
public class EmpContact {
public int id {get;set;}
public int Mobile {get;set;}
}
Target:
Main Class:
public class EMPInfo {
public int id {get;set;}
public int EmpName {get;set;}
public EMPPerdata Employeedata {get;set;}//Here is inner object in main class
public EmpContact EmpContactdetails {get;set;}//Here is other inner object in main class
}
public class EMPPerdata {
public String FullName {get;set;}
public EmpAdressInfo EmpAddress {get;set;}//Here is inner Object in one class
}
public class EmpAdressInfo {
public int id {get;set;}
public int Street {get;set;}
}
public class EmpContact {
public int id {get;set;}
public int Mobile {get;set;}
}
Original Answer
You'll need to create maps for both the outer and inner classes.
Ex:
Mapper.CreateMap<Emp,EmpInfo>();
Mapper.CreateMap<EmpDetails,EMPPerdata>();
etc...
Then:
EmpInfo Dest = Mapper.Map<Emp, EmpInfo>(srcObj);
That should work.
Edit (because that's not a great answer)
So, just wanted to add on this edit to show a few AutoMapper best practices because if you follow those best practices it's a lot easier to use.
AutoMapper Profiles
When using Automapper, unless you are mapping really really simple objects, the best way to set up your mapping is through using profiles. To use those profiles make certain to add a using AutoMapper; to your program and then you can create a mapping profile like so:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<EMP, EMPInfo>();
}
}
Now, in any of the classes you want to use this profile you can use it like so:
Mapper.Initialize(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
EMPInfo empInfo = Mapper.Map<EMP, EMPInfo>(emp);
Now... why would you ever go to all of the trouble to do this? You could have just as easily said Mapper.CreateMap<EMP, EMPInfo>(); Mapper.Map<EMP, EMPInfo(src); and it would be doing the same thing.
The benefits to doing it the way I stated above are three-fold.
Centralization of your mapping expressions. In my case, I usually have a single MappingProfiles class that holds all the Mapping profiles that I could use. This practice helps more to organize your code than anything else.
Reusability. Wherever in your program you need to use that mapping profile which will likely be more than just a single CreateMap() line you can just initialize that profile and then use it.
Unit Testing. Loading in profiles makes unit testing for AutoMapper really easy. And, unit testing will help you figure out exactly how to map nested objects.
Unit Testing AutoMapper
In your unit testing project (add one if you don't have it, it's a default project template in visual studio) you should have a class that holds the following unit test:
[TestMethod]
public void AutoMapperConfigIsValid()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<MappingProfile>();
// add all profiles you'll use in your project here
});
Mapper.AssertConfigurationIsValid();
}
Now, if I were to run that unit test using the classes you laid out above and the mapping profile that I put above it will give me the error:
Unmapped Properties:
id
EmpName
This isn't the full error, just the most important part. What's happening is... AutoMapper cannot figure out where the id and EmpName fields on the EMPInfo Type should be mapped from.
To resolve this error we need to add mapping for particular members to our profile. Our profile becomes:
CreateMap<Source.EMP, Target.EMPInfo>()
.ForMember(dest => dest.id, opt => opt.MapFrom(src => src.empid))
.ForMember(dest => dest.EmpName, opt => opt.MapFrom(src => src.Name));
We add that in... run the test again... get a new error.
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type TestStuff.Target.EmpContact.
Context:
Mapping from type TestStuff.Source.EmpContact to TestStuff.Target.EmpContact
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
This tells us that we need to add in a map for the nested members of the objects we are mapping.
To resolve this, we add the following map to the mapping profile:
CreateMap<Source.EmpContact, Target.EmpContact>();
Now what you need to do here, and this is sort of an arduous, but necessary, process, is keep running that configuration test and fixing the issues that it gives you until the test does not fail.
When all the issues are fixed, the mapping profile looks like this:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Source.EMP, Target.EMPInfo>()
.ForMember(dest => dest.id, opt => opt.MapFrom(src => src.empid))
.ForMember(dest => dest.EmpName, opt => opt.MapFrom(src => src.Name));
CreateMap<Source.EmpContact, Target.EmpContact>();
CreateMap<Source.EMPDetails, Target.EMPPerdata>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));
CreateMap<Source.EmpAdd, Target.EmpAdressInfo>();
}
}
This mapping profile works great and will map your two objects fine.
Now, this map seem to be a lot of work for a little thing, but the benefits of doing it this way come with scale. If you are ever using AutoMapper in a large 'Adapter' type project, code structured this way is going to be easier to navigate, read and test.
Hopefully this helps a bit. Sorry if I went way too far at any point here but hopefully this answer can help anyone trying to use AutoMapper.
You can find documentation on some of the stuff I mentioned above in the AutoMapper documentation as well: https://github.com/AutoMapper/AutoMapper/wiki/Configuration
you will need to create maps for all your models
Mapper.CreateMap<EMP,EmpInfo>();
Mapper.CreateMap<EmpDetails,EMPPerdata>();
Mapper.CreateMap<EmpContact,EmpContact>();
Mapper.CreateMap<EmpAdd,EmpAdressInfo>();
then:
Mapper.Map<Emp, EmpInfo>(someEmp);

Automapper convention based mapping for collection

I have a project where I am trying to map a dictionary to a ViewModel.NamedProperty. I am trying to use an AutoMapper custom resolver to perform the mapping based on a convention. My convention is that if the named property exists for the source dictionary key then map a property from the dictionary's value. Here are my example classes:
class User
{
string Name {get;set;}
Dictionary<string, AccountProp> CustomProperties {get;set;}
}
class AccountProp
{
string PropertyValue {get;set;}
//Some other properties
}
class UserViewModel
{
string Name {get;set;}
DateTime LastLogin {get;set;}
string City {get;set}
}
var user = new User()
{
Name = "Bob"
};
user.CustomProperties.Add("LastLogin", new AccountProp(){PropertyValue = DateTime.Now};
user.CustomProperties.Add("City", new AccountProp(){PropertyValue = "SomeWhere"};
I want to map the User CustomProperties dictionary to the flattened UserViewModel by convention for all properties and I do not want to specify each property individually for the mapping.
What is the best way to go about this? I was thinking Custom value resolver but it seems that I have to specify each member I want to map individually. If I wanted to do that I would just manually perform the mapping without AutoMapper.
Below is code that serve the purpose. Not sure whether it is good or not.
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) // Optional
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.CustomProperties.FirstOrDefault(x => x.Key == "City").Value.PropertyValue.ToString())) // Handle null
.ForMember(dest => dest.LastLogin, opt => opt.MapFrom(src => Convert.ToDateTime(src.CustomProperties.FirstOrDefault(x => x.Key == "LastLogin").Value.PropertyValue))); //Handle null
I ended up creating a custom type converter to deal with this scenario and it works great:
public class ObjectToPropertyTypeConverter<TFromEntity> : ITypeConverter<TFromEntity, HashSet<Property>>
{
//perform custom conversion here
}
I then implemented the Custom mapping as follows:
AutoMapper.Mapper.CreateMap<MyViewModel, HashSet<Property>>()
.ConvertUsing<ObjectToPropertyTypeConverter<MyViewModel>>();

Categories