Business Object to DTO conversion - c#

I have a list of types of application. I want to transform that object to an object of type ApplicationDTO. Inside that Application business object, there is a list of Applicants of the type Applicant. Now my DTO has the same list but I am struggling with how to assign the list members of the business object to the list inside the DTO. I have had multiple such occurrences where it is not known how many items I have on the list.
Here is an example:
// List of business objects
List<Application> ApplicationList = await _dbContextDigitVP.Applications.FromSqlRaw("Exec dbo.GetApplication {0}", id).ToListAsync();
//DTO object
ApplicationDTO applicationDTO = new ApplicationDTO
{
ApplicationNumber = Application.ApplicationNumber,
Country = Application.Country,
ApplicationUuid = Application.ApplicationUuid,
PwEmployeeAdUserName = Application.PwEmployeeAdUserName,
Category = new ApplicationCategoryDTO
{
Category = Application.Category?.Category
},
Applicants = new List<ApplicantDTO>()
{
// add members of the business object
}
};
I could go over it with a for loop but is there a way to do this inside the object definition?

You can use AutoMapper.
Once you have your types you can create a map for the two types using a MapperConfiguration and CreateMap. You only need one MapperConfiguration instance typically per AppDomain and should be instantiated during startup.
var config = new MapperConfiguration(cfg => cfg.CreateMap<Application,
ApplicationDTO>());
The type on the left is the source type, and the type on the right is the destination type. To perform a mapping, call one of the Map overloads:
var mapper = config.CreateMapper();
// or
var mapper = new Mapper(config);
ApplicationDTO dto = mapper.Map<ApplicationDTO>(Application);

you can also use LINQ to transform objects like without using AutoMapper.
List<ApplicationDTO> applicationDTOList = ApplicationList.Select(app => new ApplicationDTO
{
ApplicationNumber = app.ApplicationNumber,
Country = app.Country,
ApplicationUuid = app.ApplicationUuid,
PwEmployeeAdUserName = app.PwEmployeeAdUserName,
Category = new ApplicationCategoryDTO
{
Category = app.Category?.Category
},
Applicants = app.Applicants.Select(a => new ApplicantDTO
{
// same logic as the above
}).ToList()
}).ToList();

You can use Automapper to map data from one object to another.
Create Automapper configurations like this for the configure mapping between the ApplicationDTO and Application, ApplicationCategory and ApplicationCategoryDTO and Applicant to ApplicantDTO
var configuration = new MapperConfiguration(cfg => {
//Source ==> Desti
//Application and ApplicationDTO classes
cfg.CreateMap < Application, ApplicationDTO > ()
.ForMember(dest => dest.Category, opt => opt.MapFrom(src => src.Category))
.ForMember(dest => dest.Applicants, opt => opt.MapFrom(src => src.Applicants));
//ApplicationCategory and ApplicationCategoryDTO classes
cfg.CreateMap < ApplicationCategory, ApplicationCategoryDTO > ();
//Applicant and ApplicantDTO classes
cfg.CreateMap < Applicant, ApplicantDTO > ();
});
In your constructor, you can initialize Mapper like
private readonly IMapper _Mapper;
public TestController(IMapper mapper) {
_Mapper = mapper;
}
When you doing the casting you can do somthing like this,
var applicationDTOList = _Mapper.Map<List<ApplicationDTO>>(ApplicationList);

Related

Is there a way to pass destination object to ForAllMembers method of AutoMapperProfile object

I am just learning how to use AutoMapper utility.
Below is my AutoMapperProfile class. Here is how I call my mapper to map model object to user object:
var user = _mapper.Map<User>(model);
I would like to initialize my destination object(user) with some properties before calling var user = _mapper.Map<User>(model);. Then I would like to call the mapper var user = _mapper.Map<User>(model); and pass the user object to the ForAllMembers (from the code below - see AutoMapperProfile) and preserve those initialized properties during mapping process. The problem I have is that when ForAllMembers is called my destination object has all properties set to null initially. Is there a way to pass that object to ForAllMembers t?
using AutoMapper;
using coreapi.requests;
namespace WebApi.Helpers
{
public class AutoMapperProfile : Profile
{
// mappings between model and entity objects
public AutoMapperProfile()
{
CreateMap<UserRequest, User>()
.ForMember(dst => dst.Blah, o => o.MapFrom(src => src.Test))
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore null role
if (x.DestinationMember.Name == "Blah" && src.Test == null)
return false;
return true;
}
));
}
}
}

Can't access automapper context items after upgrade to 9

I have a mapper like this:
CreateMap<Source, ICollection<Dest>>()
.ConvertUsing((src, dst, context) =>
{
return context.Mapper.Map<ICollection<Dest>>
(new SourceItem[] { src.Item1, src.Item2 ... }.Where(item => SomeFilter(item)),
opts => opts.Items["SomethingFromSource"] = src.Something);
});
CreateMap<SourceItem, Dest>()
.ForMember(d => d.Something, opts => opts.MapFrom((src, dst, dstItem, context)
=> (string)context.Items["SomethingFromSource"]));
This gives me an exception saying You must use a Map overload that takes Action<IMappingOperationOptions>. Well, I do use the Map overload that takes this action. How else can I do this?
This is because of this change:
https://github.com/AutoMapper/AutoMapper/pull/3150
You can get the the Items by accessing the ResolutionContext's Options property.
Change context.Items["SomethingFromSource"] to context.Options.Items["SomethingFromSource"].
When there is no Items, the ResolutionContext is the same with DefaultContext. Therefore The ResolutionContext.Items property will throw the exception.
However, if there is, the ResolutionContext.Items wouldn't be the same with DefaultContext. Therefore the ResolutionContext.Items will return the list.
While ResolutionContext.Options.Items will always return the list, it would not throw any exception, whether it's empty or not. I believe it's what the error message meant, because ResolutionContext.Options is an IMappingOperationOptions
This extension can help with migrating to AutoMapper 12
public static class AutoMapperExtensions
{
public static TDestination MapOptions<TDestination>(this IMapper mapper, object source, Action<IMappingOperationOptions<object, TDestination>> OptionalOptions = null)
{
return mapper.Map(source, OptionalOptions ?? (_ => {}) );
}
}
In a Class where IMapper has been injected
public class Command
{
protected readonly IMapper AutoMapper;
public Command(IMapper mapper)
{
AutoMapper = mapper;
}
private SomethingToDo()
{
var list = new List<string>();
// change for 12
var result = AutoMapper.MapOptions<IList<Item>>(list);
// prior to 12
//var result = AutoMapper.Map<IList<Item>>(list);
}
}
Several points of consideration when you use inner mapper (i.e. context.Mapper)
First, try not to use context.Mapper.Map<TDestination>(...), use context.Mapper.Map<TSource, TDestination>(...) instead, it behaves much better.
Second, use of context in inner mappers will break encapsulation. If you need to set the values in inner objects, consider these two solutions:
In case you want to set the values after the inner map
context.Mapper.Map<Source, Dest> (source, opts => opts.AfterMap((s, d) =>
d.Something = source.Something))
In case you want to set the values before the inner map
context.Mapper.Map<Source, Dest> (source, new Dest()
{
Something = source.Something
})

Automapper configuration between two types

I have class(its part of it):
Filter { string CashierId; }
And
ClientQuery { IList<Person> Persons; }
Where Person looks:
Person {
SolvingPersonIn();
SolvingPersonIn(string solvingPersonXnuc = null);
string SolvingPersonXnuc { get; set; }
}
And how I can configure Automapper to map my Filter to ClientQuery?
Something like this:
cashierId = "12345678";
ClientQuery.Persons should be one element with "12345678"
Automapper is an object-object mapper, that means from an object it will convert it to another object. You can apply filter when you are converting data like this in this thread example our to filtering a collection:
Mapper.CreateMap<Customer, CustomerViewModel>()
.ForMember(dest => dest.Orders,
opt => opt.MapFrom(src => src.Orders.Where(o => !o.DeletedDate.HasValue)));
You can for example in you case take only the first one or you test your data beforehand then apply your normal mapping.
if (ClientQuery.Persons.Count(x => x.SolvingPersonXnuc) > 1)
{
// your logic here when you have more than one person with the CashierId
}
// apply your mapping here
You can have a read at the official documentation, there are a lot of examples Automapper documentation
If you only want to add a single CashierId to the List of Persons, you can create and populate a new list:
CreateMap<Filter, ClientQuery>
.ForMember(dest => dest.Persons,
o => o.ResolveUsing(src => {
return new List<Person> { new Person { SolvingPersonXnuc = src.CashierId }};
})
);

Is it possible to call Mapper.Map inside AfterMap?

Is something like this allowed somehow?
CreateMap<UserViewModel, User>()
.ForMember(u => u.Password, o => o.Ignore())
.AfterMap((src, dest) => {
...
var entity = Mapper.Map<Entity>(src.SomeProperty);
...
});
Is not working for me, it says that mapping tried to use inside AfterMap doesn't exist.
I notice that you are using the static AutoMapper class within your mapping, but are you also using the static instance outside of your mapping and does it have a mapping configured for your Entity class?
The below works, note that the context.Mapper call ensures that the same AutoMapper instance is used for both the calling Map and subsequent AfterMap methods.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<UserViewModel, User>()
.AfterMap((source, destination, context) =>
{
var entity = context.Mapper.Map<Entity>(source.SomeProperty);
});
cfg.CreateMap<EntityViewModel, Entity>();
});
var mapper = config.CreateMapper();
var viewModel = new UserViewModel
{
Name = "Test User",
SomeProperty = new EntityViewModel
{
Value = "Sub Class"
}
};
var user = mapper.Map<User>(viewModel);

How to map to a destination with a complex type ctor parameter?

Project (destination) has both public ctor with paramters and protected parameterless ctor.
public Project(String name, User initiator) {
this.Name = name;
this.Initiator = initiator;
this.InitializedOn = DateTime.Now;
}
// meant only to support some EF-related operations
protected Project() {}
The map:
Mapper.CreateMap<CreateProjectModel, Project>().ForMember(
dest => dest.Name,
opt => opt.MapFrom(src => src.ProjectName)
);
Consider the excerpt from create action:
var user = dbContext.Users.Find(someId);
// initialize model using protected ctor - that's the behavior by default
var model = Mapper.Map<Project>(project);
// then initialize some additional properties
// or initialize model as it needs to be
// var model = new Project(project.ProjectName, user);
// and populate everything by hand
Is it possible to use new Project(project.ProjectName, user) initialization and to map the rest source properties using Automapper?
Since the other constructor is based on multiple source types, you may have to invoke it explicitly and map ther remaining properties through AutoMapper:
CreateProjectModel project = ...
var model = new Project(project.ProjectName, user);
Mapper.Map(project, model);

Categories

Resources