Right now I have some models that look like this:
Consumer ResourceModel:
public ConsumerResourceModel()
{
consumerData = new List<ConsumerData>();
Memberships = new List<MemberResourceModel>();
}
public List<ConsumerData> consumerData { get; set; }
public List<MemberResourceModel> Memberships { get; set; }
ConsumerResponseModel:
public class ConsumerResponseModel
{
public List<Consumer> consumers { get; set; }
}
public class Membership
{
public string membershipType { get; set; }
}
public class Consumer
{
public decimal consumerId { get; set; }
public Name name { get; set; }
public List<Membership> memberships { get; set; }
}
My current Mapping looks like this:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(config =>
{
config.CreateMap<ConsumerData, Consumer>()
.ForMember(dest=>dest.memberships, opts=>opts.MapFrom(src=>new List<Membership>()))
.ReverseMap();
config.CreateMap<ConsumerData, Name>()
.ReverseMap();
config.CreateMap<MemberResourceModel, Membership>()
.ReverseMap();
});
}
I have everything being mapped correctly for consumer and membership but I'm basically having to do something like this in my method:
public ConsumerResponseModel MapConsumer(IEnumerable<ConsumerResourceModel> res)
{
var responseModel = new ConsumerResponseModel {consumers = new List<Consumer>()};
var consumerResourceModels = res as IList<ConsumerResourceModel> ?? res.ToList();
foreach (var item in consumerResourceModels)
{
foreach(var consumerData in item.consumerData)
{
var consumer = Mapper.Map<Consumer>(consumerData);
foreach(var membership in item.Memberships)
{
consumer.memberships.Add(Mapper.Map<Membership>(membership);
}
responseModel.consumers.Add(consumer);
}
}
return responseModel;
}
While this code is doing the mapping I need it to do and everything works fine, I'm trying to figure out how I can make one call to Mapper.Map and it just automaps everything in this collection for me instead of having this extra method iterating through and just have it all being done in my AutoMapperConfig class.
I'm having a tough time wrapping my head around how to get that piece to work as everything I have tried like doing .AfterMap in the ConsumerData, Consumer mapping just ends up creating empty memberships. I haven't mapped ConsumerResourceModel yet as I figured once I learned how to map the memberships then I could move a level up to map that all correctly.
Related
I have two sets of objects
Objects that I use in C# client application:
public class EmployeeClient
{
public int Id { get; set; }
public int DepartmentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentClient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationClient
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentClient> Departments { get; set; }
public List<EmployeeClient> Employees { get; set; }
}
And DTOs:
public class EmployeeDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class OrganizationDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentDto> Departments { get; set; }
}
I use AutoMapper and I need to configure mapping Client -> DTOs and DTOs -> Client.
I implemented mapping DTOs->Client like this:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.ResolveUsing(src => src.Departments.SelectMany(d => d.Employees)))
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client)
{
foreach (var department in dto.Departments)
{
foreach (var employee in department.Employees)
{
var clientEmployee = client.Employees.First(e => e.Id == employee.Id);
clientEmployee.DepartmentId = department.Id;
}
}
}
}
It is not universal solution, but is works for me.
I've found only one option how mapping Client->DTOs could be implemented:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto)
{
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var configuration = (IConfigurationProvider)new MapperConfiguration(cfg =>
{
cfg.AddProfiles(typeof(ClientToDtosMappingProfile));
});
var mapper = (IMapper)new Mapper(configuration);
var employeeDto = mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}
It works, but I do not like this solution because I should create instance of new Mapper every time I map objects. In my real code Employee has a lot of nested elements and mapping is configured in multiple profiles.
Any ideas how it could be implemented better?
I made my code a bit better using ResolutionContext. It allows not to create mappers in AfterMap function.
DtoToClientMappingProfile:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.Ignore())
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client, ResolutionContext resolutionContext)
{
if (dto.Departments == null)
{
return;
}
client.Departments = new List<DepartmentClient>();
foreach (var department in dto.Departments)
{
var departmentClient = resolutionContext.Mapper.Map<DepartmentClient>(department);
client.Departments.Add(departmentClient);
if (department.Employees == null)
{
continue;
}
if (client.Employees == null)
{
client.Employees = new List<EmployeeClient>();
}
foreach (var employee in department.Employees)
{
var employeeClient = resolutionContext.Mapper.Map<EmployeeClient>(employee);
employeeClient.DepartmentId = department.Id;
client.Employees.Add(employeeClient);
}
}
}
ClientToDtosMappingProfile:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto, ResolutionContext resolutionContext)
{
if (client.Employees == null)
{
return;
}
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var employeeDto = resolutionContext.Mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}
If you call AssertConfigurationIsValid, AM will complain about what it doesn't know how to map.
The problem seems to be that you don't have the information needed to fill the destination object in the source object.
You will need to add a resolver for each property AM complains about, like the ResolveUsing you already have, for example.
You also need to pass the extra information that's needed.
The result may not look good eventually because AM cannot rely on uniform objects to do its job, you have to tell it what to do.
Another way to go about it is to do the high level mapping in your own code and rely on AM only when the mapping is simple enough so AM can do it by itself. The more you customize AM, the less value you get from it.
I have a weird situation where I have objects and Lists of objects as part of my entities and contracts to interface with a third-party service. I'm going to try to see if I can replace the actual object class with something more specific in the entities and contracts to get around this, but I am curious if there is a way to get AutoMapper to handle this as is.
Here are some dummy classes:
public class From
{
public object Item { get; set; }
}
public class FromObject
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
And the quick replication:
Mapper.CreateMap<From, To>();
Mapper.CreateMap<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // FromObject
Basically, the question is this: Is there a way to get AutoMapper to understand that from.Item is a FromObject and apply the mapping to ToObject? I'm thinking there's probably not a way to make it automatic, since there's nothing that would indicate that to.Item has to be a ToObject, but is there a way to specify during the CreateMap or Map calls that this should be taken into account?
I don't think there is an "automatic" way of doing it, since AutoMapper won't be able to figure out that From.Item is FromObject and To.Item is ToObject.
But, while creating mapping, you can specify that
Mapper.CreateMap<FromObject, ToObject>();
Mapper.CreateMap<From, To>()
.ForMember(dest => dest.Item, opt => opt.MapFrom(src => Mapper.Map<ToObject>(src.Item)));
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
If you're willing to use an additional interface, this can be accomplished using Include. You can't just map object to object in this fashion, though.
public class From
{
public IItem Item { get; set; }
}
public class FromObject : IItem
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
public interface IItem
{
// Nothing; just for grouping.
}
Mapper.CreateMap<From, To>();
Mapper.CreateMap<IItem, object>()
.Include<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
Say I have this simple entity:
public class Person
{
public int PersonID { get; set; }
public string Name { get; set; }
}
The entity framework can infer by convention that the PersonID field is the primary key. However, if I give the model builder this class:
public class PersonCfg : EntityTypeConfiguration<Person>
{
public PersonCfg()
{
ToTable("Person", "Person");
HasKey(p => p.PersonID);
}
}
Would that improve startup performance? My thought was it might allow EF to do less reflecting and startup the app faster but I don't know how it works behind the scenes to know if it has any impact.
To test this, you can use the DbModelBuilder class to build the model yourself and track the speed of the "Compile" step.
Here's my example code (LinqPad script):
void Main()
{
// Initialize the overall system, but don't count the result.
BuildC();
DateTime startDateA = DateTime.Now;
BuildA();
DateTime.Now.Subtract(startDateA).TotalMilliseconds.Dump("A");
DateTime startDateB = DateTime.Now;
BuildB();
DateTime.Now.Subtract(startDateB).TotalMilliseconds.Dump("B");
}
public class PersonA
{
public int PersonAId { get; set; }
public string Name { get; set; }
}
private void BuildA()
{
var builder = new DbModelBuilder();
builder.Entity<PersonA>();
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
public class PersonB
{
public int PersonBId { get; set; }
public string Name { get; set; }
}
private void BuildB()
{
var builder = new DbModelBuilder();
builder.Conventions.Remove<IdKeyDiscoveryConvention>();
builder.Entity<PersonB>()
.HasKey(p => p.PersonBId);
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
public class PersonC
{
public int PersonCId { get; set; }
public string Name { get; set; }
}
private void BuildC()
{
var builder = new DbModelBuilder();
builder.Entity<PersonC>()
.HasKey(p => p.PersonCId);
var model = builder.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
model.Compile();
}
I get the result of 2.0004ms to 2.0009ms. Curiously, removing conventions made the operation take longer.
I'm trying to use AutoMapper to take data from a class that has prefixes before property names and map it to a second class that doesn't have those prefixes. However, I don't necessarily want it to always strip out that prefix: I just want it to do it for this particular mapping.
My source class looks like this:
public class AdvancedSearchFilterDataModel
{
// ....
public string ServiceMeterNumber { get; set; }
// ....
}
My destination class looks like this:
[DataContract]
public class ServicesAdvancedSearchFilterData : AdvancedSearchFilterData
{
// ....
[DataMember]
public string MeterNumber { get; set; }
// ....
}
When I try to map values like this, it works:
Mapper.Configuration.RecognizePrefixes("Service");
Mapper.CreateMap<AdvancedSearchFilterDataModel, ServicesAdvancedSearchFilterData>();
ServicesAdvancedSearchFilterData servciesFilterData =
Mapper.Map<ServicesAdvancedSearchFilterData>(model);
But I only want "Service" to be recognized as a prefix for certain mappings, since it's also used as a normal part of property names in other mappings. I tried to handle this with a profile, but this didn't work -- no data was mapped:
Mapper.CreateProfile("ServicePrefix").RecognizePrefixes("Service");
Mapper.CreateMap<AdvancedSearchFilterDataModel, ServicesAdvancedSearchFilterData>()
.WithProfile("ServicePrefix");
ServicesAdvancedSearchFilterData servciesFilterData =
Mapper.Map<ServicesAdvancedSearchFilterData>(model);
How can I make it recognize the prefix only when I want it to, either using profiles or some other technique? (I also have other prefixes that I'm going to need it to recognize for other mappings in the same way.)
I achieved this functionality by creating following structure:
I have Person model for my view which is flattened from PersonCombined
public class PersonCombined
{
public Person Person { get; set; }
public Address DefaultAddress { get; set; }
public Contact EmailContact { get; set; }
public Contact PhoneContact { get; set; }
public Contact WebsiteContact { get; set; }
}
public class Person : IWebServiceModel
{
public int ID { get; set; }
public string PersonFirstName { get; set; }
public string PersonSurname { get; set; }
public string PersonDescription { get; set; }
public Nullable<bool> PersonIsActive { get; set; }
}
Then I have separate class for this mapping only that looks like this:
public class PersonCustomMapping : ICustomMapping
{
const string separator = " ";
private static IMappingEngine _MappingEngine;
public IMappingEngine MappingEngine
{
get
{
if (_MappingEngine == null)
{
var configuration = new ConfigurationStore(new TypeMapFactory(), AutoMapper.Mappers.MapperRegistry.Mappers);
configuration.RecognizePrefixes("Person");
configuration.RecognizeDestinationPrefixes("Person");
configuration.CreateMap<Person, MCIACRM.Model.Combine.PersonCombined>();
configuration.CreateMap<MCIACRM.Model.Combine.PersonCombined, Person>();
_MappingEngine = new MappingEngine(configuration);
}
return _MappingEngine;
}
}
}
In my generic view I have mappingEngine property like this:
private IMappingEngine mappingEngine
{
get
{
if (_mappingEngine == null)
{
_mappingEngine = AutoMapper.Mapper.Engine;
}
return _mappingEngine;
}
}
Finally in my generic view constructor i have:
public GenericEntityController(IGenericLogic<S> logic, ICustomMapping customMapping)
: base()
{
this._mappingEngine = customMapping.MappingEngine;
this.logic = logic;
}
And that's how I do mapping:
result = items.Project(mappingEngine).To<R>();
or
logic.Update(mappingEngine.Map<S>(wsItem));
Because I use 1 entity per view I can define custom mapping configuration per entity.
Hope this helps
Here is my data transfer object
public class LoadSourceDetail
{
public string LoadSourceCode { get; set; }
public string LoadSourceDesc { get; set; }
public IEnumerable<ReportingEntityDetail> ReportingEntity { get; set; }
}
public class ReportingEntityDetail
{
public string ReportingEntityCode { get; set; }
public string ReportingEntityDesc { get; set; }
}
And here is my ViewModel
public class LoadSourceViewModel
{
#region Construction
public LoadSourceViewModel ()
{
}
public LoadSourceViewModel(LoadSourceDetail data)
{
if (data != null)
{
LoadSourceCode = data.LoadSourceCode;
LoadSourceDesc = data.LoadSourceDesc;
ReportingEntity = // <-- ? not sure how to do this
};
}
#endregion
public string LoadSourceCode { get; set; }
public string LoadSourceDesc { get; set; }
public IEnumerable<ReportingEntityViewModel> ReportingEntity { get; set; }
}
public class ReportingEntityViewModel
{
public string ReportingEntityCode { get; set; }
public string ReportingEntityDesc { get; set; }
}
}
I'm not sure how to transfer the data from the LoadSourceDetail ReportingEntity to the LoadSourceViewModel ReportingEntity. I'm trying to transfer data from one IEnumerable to another IEnumerable.
I would use AutoMapper to do this:
https://github.com/AutoMapper/AutoMapper
http://automapper.org/
You can easily map collections, see https://github.com/AutoMapper/AutoMapper/wiki/Lists-and-arrays
It would look something like this:
var viewLoadSources = Mapper.Map<IEnumerable<LoadSourceDetail>, IEnumerable<LoadSourceViewModel>>(loadSources);
If you are using this in an MVC project I usually have an AutoMapper config in the App_Start that sets the configuration i.e. fields that do not match etc.
Without AutoMapper you will have to map each property one by one ,
Something like this :
LoadSourceDetail obj = FillLoadSourceDetail ();// fill from source or somewhere
// check for null before
ReportingEntity = obj.ReportingEntity
.Select(x => new ReportingEntityViewModel()
{
ReportingEntityCode = x.ReportingEntityCode,
ReportingEntityDesc x.ReportingEntityDesc
})
.ToList(); // here is 'x' is of type ReportingEntityDetail
You could point it to the same IEnumerable:
ReportingEntity = data.ReportingEntity;
If you want to make a deep copy, you could use ToList(), or ToArray():
ReportingEntity = data.ReportingEntity.ToList();
That will materialize the IEnumerable and store a snapshot in your view model.