How to map a single object from multiple ones, using AutoMapper? - c#

AssetAvailability
public class AssetAvailabilityDto
{
public int Id { get; set; }
public int AssetId { get; set; }
public int CalendarId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Asset
public class AssetDto
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public int OwnedBy { get; set; }
public DateTime LastModifiedAt { get; set; }
}
BookingSchedule
public class BookingScheduleDto
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
The destination to hold them all
public class AssetInformationDto
{
public int AssetId { get; set; }
public int CategoryId { get; set; }
public string AssetName { get; set; }
public string AssetDescription { get; set; }
public bool AssetIsActive { get; set; }
public int AssetOwnedBy { get; set; }
public DateTime AssetLastModifiedAt { get; set; }
// End of Assets
public int AvailabilityId { get; set; }
public DateTime AvailabilityStartDate { get; set; }
public DateTime AvailabilityEndDate { get; set; }
// End of Availability
public List<DateTime> BookingStartDate { get; set; } = new List<DateTime>();
public List<DateTime> BookingEndDate { get; set; } = new List<DateTime>();
// End of Booking schedule
}
Where the mapping takes place
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The configuration of AutoMapper
CreateMap<AssetDto, AssetInformationDto>()
.ForMember(dest => dest.AssetName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.AssetDescription, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.AssetLastModifiedAt, opt => opt.MapFrom(src => src.LastModifiedAt))
.ForMember(dest => dest.AssetIsActive, opt => opt.MapFrom(src => src.IsActive))
.ForMember(dest => dest.AssetOwnedBy, opt => opt.MapFrom(src => src.OwnedBy))
.ForMember(dest => dest.AssetId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<AssetAvailabilityDto, AssetInformationDto>()
.ForMember(dest => dest.AvailabilityStartDate, opt => opt.MapFrom(src => src.StartDate))
.ForMember(dest => dest.AvailabilityEndDate, opt => opt.MapFrom(src => src.EndDate))
.ForMember(dest => dest.AvailabilityId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<IEnumerable<BookingScheduleDto>, AssetInformationDto>().AfterMap((s, d) =>
{
foreach (var item in s)
{
d.BookingStartDate.Add(item.StartDate);
d.BookingEndDate.Add(item.EndDate);
}
}).ForAllOtherMembers(m => m.Ignore());
This is being done on an ASP.NET Core 2.2 project, hence why AutoMapper is always injected with dependency injection (_mapper is the injected object)
The post shared here https://stackoverflow.com/a/38257912/11272124 is a solution to the above problem, by making a function "EntityMapper" and by slightly modifying it to accept the injected _mapper object, but I'd like to learn an alternative solution.
Isn't this possible with simply configuring AutoMapper to do so?
Maybe the reason for it not to work, is due to my poorly written out configuration.
The problem here is that after each mapped object, (After every information = ...) the previously assigned properties are reverted to their type's default values. That's why I included ForAllOtherMembers.Ignore() in the respective class where I configure how the mapping is done.

What I've been looking for was the usage of Mapper.Map(TSource, TDestination) (Mapper in my case would be _mapper, due to dependency injection.)
It executes a mapping from the source object to the existing destination object.
The method I previously used (Mapper.Map<TDestination>(TSource)) executes a mapping from the source object to a new destination object.
The code before:
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The code that fixed my issue
AssetInformationDto information;
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability =
_assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule =
_assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
_mapper.Map(assetAvailability, information);
_mapper.Map(schedule, information);

Related

Nested collection mapping fails with automapper

Somehow the mapping fails with the error message:
One or more errors occurred. (The following member on Data.Shop cannot be mapped:
Images
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type Data.Shop.
After having spent some time tinkering with it and searching for a solution on the official documentation + SO, I feel like I either messed up / forgot with a particular call in the profile, or need to resort to creating a custom map.. What do you guys think?
Configuration code that I used:
Entities
public class Shop
{
public Guid Id { get; set; }
public string Name { get; set; }
public Specialty Specialty { get; set; } = new Specialty();
public Address Address { get; set; } = new Address();
public string Owner { get; set; }
public List<Image> Images { get; set; } = new List<Image>();
public List<Tag> Tags { get; set; } = new List<Tag>();
public List<Promotion> Promotions { get; set; } = new List<Promotion>();
}
public class Image
{
public Guid Id { get; set; }
public string Title { get; set; }
public byte[] ImageData { get; set; }
}
DTOs
public class ShopDto
{
[Required]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Specialty { get; set; }
[Required]
public int HouseNumber { get; set; }
[Required]
public string Street { get; set; }
[Required]
public string City { get; set; }
[Required]
public short Plz { get; set; }
[Required]
public string Owner { get; set; }
public List<ImageDto> Images { get; set; } = new List<ImageDto>();
public List<TagDto> Tags { get; set; } = new List<TagDto>();
public List<PromotionDto> Promotions { get; set; } = new List<PromotionDto>();
}
public class ImageDto
{
[Required]
public Guid Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public byte[] ImageData { get; set; }
}
Profile code
CreateMap<Image, ImageDto>();
CreateMap<Shop, ShopDto>()
.ForMember(dest => dest.City, member => member.MapFrom(source => source.Address.City))
.ForMember(dest => dest.Plz, member => member.MapFrom(source => source.Address.Plz))
.ForMember(dest => dest.Street, member => member.MapFrom(source => source.Address.Street))
.ForMember(dest => dest.HouseNumber, member => member.MapFrom(source => source.Address.HouseNumber))
.ForMember(dest => dest.Images, opt => opt.MapFrom(source => source.Images))
.ReverseMap()
.ForMember(src => src.Id, dest => dest.Ignore())
.ForMember(src => src.Promotions, dest => dest.MapFrom(src => src.Promotions))
.ForMember(src => src.Tags, dest => dest.MapFrom(src => src.Tags))
.ForMember(src => src.Images, dest => dest.MapFrom(src => src.Images))
.ForPath(src => src.Specialty.Name, dest => dest.MapFrom(src => src.Specialty))
.ForPath(src => src.Address.City, dest => dest.MapFrom(src => src.City))
.ForPath(src => src.Address.Plz, dest => dest.MapFrom(src => src.Plz))
.ForPath(src => src.Address.Street, dest => dest.MapFrom(src => src.Street))
.ForPath(src => src.Address.HouseNumber, dest => dest.MapFrom(src => src.HouseNumber));
You have defined a mapping from Shop to ShopDto but appear to be attempting to map ShopDto to Shop. You need define mappings in both directions or swap the template arguments. Of course you also need a mapping from ImageDto to Image.
Additionally you can remove this part:
.ForMember(src => src.Images, dest => dest.MapFrom(src => src.Images))
AutoMapper is smart enough to map collections if it has a mapping for the collection element type.

How to use Automapper to flatten list of entity hierarchies?

I want to use automapper to flatten a list of entity heirarchies returned back from Entity Framework Core.
Here are my entities:
public class Employee {
public int Id { get; set; }
[Required]
public string Name { get; set; }
public double? PayRateRegular { get; set; }
public double? PayRateLoadedRegular { get; set; }
public double? GMOutput { get; set; }
public string EmployeeType { get; set; }
//List of CommissionDetails where this employee is the consultant
public IEnumerable<CommissionDetail> CommissionDetailConsultants { get; set; } = new List<CommissionDetail>();
}
public class Project {
public int Id { get; set; }
public string Description { get; set; }
public double? BillRateRegular { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public Customer Customer { get; set; }
public int CustomerId { get; set; }
}
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
}
public class CommissionDetail {
public string SaleType { get; set; }
public double CommissionPercent { get; set; }
public bool? IsReported { get; set; }
public int? Level { get; set; }
public string BasedOn { get; set; }
public Project Project { get; set; }
public int ProjectId { get; set; }
public Employee SalesPerson { get; set; }
public int SalesPersonEmployeeId { get; set; }
public Employee Consultant { get; set; }
public int ConsultantEmployeeId { get; set; }
}
Here is my DTO:
public class ConsultantGridViewModel
{
public string ConsultantName { get; set; }
public string CustomerName { get; set; }
public string SalesPersonName { get; set; }
public string ProjectDescription { get; set; }
public double? PayRate { get; set; }
public double? LoadedRated { get; set; }
public double? BillRate { get; set; }
public double? GM { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public double CommissionPercent { get; set; }
public int? CommissionLevel { get; set; }
}
Here is my call to EF:
return await _dbContext.Employee
.AsNoTracking()
.Include(e => e.CommissionDetailConsultants)
.ThenInclude(cd => cd.SalesPerson)
.Include(e => e.CommissionDetailConsultants)
.ThenInclude(cd => cd.Project)
.ThenInclude(p => p.Customer)
.Where(e => e.EmployeeType == "Contractor")
.ToListAsync();
I'm currently flattening it with SelectMany as follows:
var consultants = employees.SelectMany(e =>
e.CommissionDetailConsultants,
(emp, com) => new ConsultantGridViewModel {
ConsultantName = emp.Name,
PayRate = emp.PayRateRegular,
LoadedRated = emp.PayRateLoadedRegular,
GM = emp.GMOutput,
BillRate = com.Project.BillRateRegular,
ProjectDescription = com.Project.Description,
ProjectStartDate = com.Project.StartDate,
ProjectEndDate = com.Project.EndDate,
CustomerName = com.Project.Customer.Name,
SalesPersonName = com.SalesPerson.Name,
CommissionPercent = com.CommissionPercent,
CommissionLevel = com.Level
});
I would like to use automapper instead. I've used automapper for all my other DTO mappings but I can't figure out how to use it to flatten a nested object like this.
Let rewrite what you have currently with SelectMany + Select utilizing the Consultant navigation property:
var consultants = employees
.SelectMany(e => e.CommissionDetailConsultants)
.Select(com => new ConsultantGridViewModel
{
ConsultantName = com.Consultant.Name,
PayRate = com.Consultant.PayRateRegular,
LoadedRated = com.Consultant.PayRateLoadedRegular,
GM = com.Consultant.GMOutput,
BillRate = com.Project.BillRateRegular,
ProjectDescription = com.Project.Description,
ProjectStartDate = com.Project.StartDate,
ProjectEndDate = com.Project.EndDate,
CustomerName = com.Project.Customer.Name,
SalesPersonName = com.SalesPerson.Name,
CommissionPercent = com.CommissionPercent,
CommissionLevel = com.Level
});
Now it can be seen that the CommissionDetail contains all the necessary data, so while you can't avoid SelectMany, you can replace the Select by creating a mapping from CommissionDetail to ConsultantGridViewModel and use something like this:
var consultants = Mapper.Map<List<ConsultantGridViewModel>>(
employees.SelectMany(e => e.CommissionDetailConsultants));
or even better, project directly to the DTO:
var consultants = await _dbContext.Employee
.Where(e => e.EmployeeType == "Contractor")
.SelectMany(e => e.CommissionDetailConsultants)
.ProjectTo<ConsultantGridViewModel>()
.ToListAsync();
Now the mapping.
AutoMapper will map automatically members like CommisionPercent. Also the Flattening feature will handle automatically mappings like Project.EndDate -> ProjectEndDate, Consultant.Name -> ConsultantName etc.
So as usual with AutoMapper you should specify manually the mapping of properties which don't fall into previous categories. The minimal configuration in this case would be something like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CommissionDetail, ConsultantGridViewModel>()
.ForMember(dst => dst.PayRate, opt => opt.MapFrom(src => src.Consultant.PayRateRegular))
.ForMember(dst => dst.LoadedRated, opt => opt.MapFrom(src => src.Consultant.PayRateLoadedRegular))
.ForMember(dst => dst.GM, opt => opt.MapFrom(src => src.Consultant.GMOutput))
.ForMember(dst => dst.BillRate, opt => opt.MapFrom(src => src.Project.BillRateRegular))
.ForMember(dst => dst.CustomerName, opt => opt.MapFrom(src => src.Project.Customer.Name))
.ForMember(dst => dst.CommissionLevel, opt => opt.MapFrom(src => src.Level));
});
P.S. You can even avoid SelectMany by basing your queries directly on CommissionDetail entity, for instance
var consultants = await _dbContext.Set<CommissionDetail>()
.Where(c => c.Consultant.EmployeeType == "Contractor")
.ProjectTo<ConsultantGridViewModel>()
.ToListAsync();
Note that when you do direct projection, there is no need of AsNoTracking or Include / ThenInclude.

Automapper many to many mapping in 3-Tier architecture

I have two Entities model on DataAccessLayer:
Consumer
public class Consumer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<Contractor> Contractors { get; set; }
public Consumer()
{
Contractors = new List<Contractor>();
}
}
Contractor
public class Contractor
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public virtual ICollection<Consumer> Consumers { get; set; }
public Contractor()
{
Consumers = new List<Consumer>();
}
}
...and two class on Transfer Data Layer:
ConsumerTransfer
public class ConsumerTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public IList<ContractorTransfer> ContractorTransfer { get; set; }
}
ContractorTransfer
public class ContractorTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
}
**
How can I get data from Consumer to ConsumerTransfer?
**
I tried to do it this way
public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg => cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto=>dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors))
);
return Mapper.Map<IEnumerable<Consumer>, IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}
First of all, I'd rename your "ContractorTransfer" property to "Contractors". Generally I keep property names the same between DTOs and source types. Not a 100% rule, but unless I have a REALLY good reason on the client side (serialization concerns, whatever), then I don't change property names.
Second, your AutoMapper config is in the wrong place. You need to put the Initialize in the startup of your application, not right next to your mapping.
Third, your configuration is way too verbose. AutoMapper automatically maps properties with the same name.
Fourth, you're missing a map for the second set of source/destination types. Assuming you've fixed your inconsistent property name, here's your config:
Mapper.Initialize(cfg => {
cfg.CreateMap<Consumer, ConsumerTransfer>();
cfg.CreateMap<Contract, ContractTransfer>();
});
Then later on when you execute the map:
Mapper.Map<Consumer, ConsumerTransfer>(consumer);
That's it.
public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors));
cfg.CreateMap<Contractor, ContractorTransfer>();
});
return Mapper.Map<IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}

Automapper complex types mapping exception

I'm trying to implement the AutoMapper for a new module.
I have the MVC model at the web site, I'm working on, and it looks like this:
public class MvcModel
{
public Params Params { get; set; }
public Steps Steps { get; set; }
}
public class Params
{
public int? RequestId { get; set; }
public bool NewClient { get; set; }
}
public class Steps
{
public Step1 Step1 { get; set; }
public Step2 Step2 { get; set; }
public Step3 Step3 { get; set; }
}
public class Step1
{
public int Name { get; set; }
}
public class Step2
{
public int Phone { get; set; }
}
public class Step3
{
public int Email { get; set; }
}
For the other hand I have next class, that I have to pass to some service:
public class Request
{
public Parameters Parameters { get; set; }
public RequestContent RequestContent { get; set; }
}
public class Parameters
{
public int NewClient { get; set; }
}
public class RequestContent
{
public int Id { get; set; }
public int InnerId { get; set; }
public string Session { get; set; }
public string Clerk { get; set; }
public bool Private { get; set; }
public PersonalDetails PersonalDetails { get; set; }
public Phones Phones { get; set; }
public ElectonicCommunication ElectonicCommunication { get; set; }
}
public class PersonalDetails
{
public int Name { get; set; }
}
public class Phones
{
public int Phone { get; set; }
}
public class ElectonicCommunication
{
public int Email { get; set; }
}
I've created a new AutoMapper configuration class (that is called from Global.asax):
public class AutoMapperConfig
{
public static void Configure()
{
MapperConfiguration MapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile<Out>();
cfg.AddProfile<In>();
cfg.CreateMap<MvcModel, Request>();
});
MapperConfiguration.AssertConfigurationIsValid();
}
}
public class Out: Profile
{
protected override void Configure()
{
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}
public class In: Profile
{
protected override void Configure()
{
CreateMap<Request, MvcModel>()
.ForMember(dest => dest.Params, opt => opt.MapFrom(src => src.Parameters))
.ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.RequestContent));
}
}
Those are schematically similar, though the real object are a bit bigger and have different names.
Excpectations: MvcModel to be mapped to Request and backwards.
To be more accurate:
MvcModel.Params to be mapped to Request.Parameters and backwards
MvcModel.Steps to be mapped to Request.RequestContent and backwards
InnerId, Session, Clerk, Private from Request.RequestContent must be ignored
The problem: I'm getting an error, when on application start:
The following property on ... cannot be mapped:
Add a custom mapping expression, ignore, add a custom resolver, or modify
the destination type ...
Context:
Mapping to property ... from ... to ...
Mapping from type ... to ...
from source type AutoMapperMessageBugTests.SourceChild Mapping to type
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
I didn't find good documentation on how to use AutoMapper globally for the application, and just can't figure how to use it properly and what exactly I'm doing wrong.
I couldn't find a good example of complex objects mapping ether...
Can someone help me with this?
Thank's to stuartd this are working now!
The thing that I didn't understand was, that I have to map bottom to top! All the sub-objects have to mapped first, so they would be recognized at the moment parent objects are mapped!
Now profile's looks like this:
public class Out: Profile
{
protected override void Configure()
{
CreateMap<Step1, PersonalDetails>();
CreateMap<Step2, Phones>();
CreateMap<Step3, ElectonicCommunication>();
CreateMap<Params, Parameters>();
CreateMap<Params, RequestContent>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.RequestId))
.ForAllMembers(opt => opt.Ignore());
CreateMap<Steps, RequestContent>()
.ForMember(dest => dest.PersonalDetails, opt => opt.MapFrom(src => src.Step1))
.ForMember(dest => dest.Phones, opt => opt.MapFrom(src => src.Step2))
.ForMember(dest => dest.ElectonicCommunication, opt => opt.MapFrom(src => src.Step3))
.ForAllMembers(opt => opt.Ignore());
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}

AutoMapper referencing a map within a map

Really struggling with how I might accomplish this, I'm basically trying to feed my Index view a list of ClubViewModel but to do this I need to match up my list of Club. I'm using AutoMapper and everything is working well apart from the CalendarEntry stuff which is a List in my database model and a flattened class in my view model.
I don't really want to change my view model around as I have the Create view in the way I want it (bools for each day and associated start/finish times).
First off here is the controller which is successfully mapping most of the class:
public ActionResult Index()
{
var clubsList = _service.GetAllClubs();
IList<ClubViewModel> clubsViewModelList = new List<ClubViewModel>();
foreach (var item in clubsList)
{
clubsViewModelList.Add(AutoMapper.Mapper.Map<ClubViewModel>(item));
}
return View(clubsViewModelList);
}
Here are my Repository Models and Enums and also my Web ViewModels
public class Club
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public List<CalendarEntry> MeetingDays { get; set; }
}
public class CalendarEntry
{
public int CalendarEntryId { get; set; }
public int ClubId { get; set; }
public Days Day { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
public enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
public class ClubViewModel
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public CalendarEntryViewModel CalendarEntryViewModel { get; set; }
}
public class CalendarEntryViewModel
{
public bool IsMonday { get; set; }
public bool IsTuesday { get; set; }
public bool IsWednesday { get; set; }
public bool IsThursday { get; set; }
public bool IsFriday { get; set; }
public DateTime? MondayFrom { get; set; }
public DateTime? MondayTo { get; set; }
public DateTime? TuesdayFrom { get; set; }
public DateTime? TuesdayTo { get; set; }
public DateTime? WednesdayFrom { get; set; }
public DateTime? WednesdayTo { get; set; }
public DateTime? ThursdayFrom { get; set; }
public DateTime? ThursdayTo { get; set; }
public DateTime? FridayFrom { get; set; }
public DateTime? FridayTo { get; set; }
}
Finally here is my AutoMapper configuration method:
public static void RegisterMappings()
{
Mapper.CreateMap<Club, ClubViewModel>()
.ForMember(dest => dest.CalendarEntryViewModel,
opts => opts.Ignore());
Mapper.CreateMap<CalendarEntry, CalendarEntryViewModel>()
.ForMember(dest => dest.IsMonday, opts => opts.Equals(true))
.ForMember(dest => dest.IsTuesday, opts => opts.Equals(true))
.ForMember(dest => dest.IsWednesday, opts => opts.Equals(true))
.ForMember(dest => dest.IsThursday, opts => opts.Equals(true))
.ForMember(dest => dest.IsFriday, opts => opts.Equals(true))
.ForMember(dest => dest.MondayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.MondayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.TuesdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.TuesdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.WednesdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.WednesdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.ThursdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.ThursdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.FridayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.FridayTo, opts => opts.Equals(DateTime.Now));
Mapper.AssertConfigurationIsValid();
}
For the time being I have just hardcoded the values of the bools/dates whilst I try and get things working.
Is there anyway I can somehow add the second CalendarEntry to CalendarEntryViewModel Map to the Club to ClubViewModel Map as an additional member or am I doing this completely wrong?
You should unpack the CalendarEntryViewModel so that it more closely represents a calendar entry in your domain. You're currently trying to shove a list into a flattened object .. which will set you up for even more pain later.
Before I get to that, I have to mention:
foreach (var item in clubsList)
{
clubsViewModelList.Add(AutoMapper.Mapper.Map<ClubViewModel>(item));
}
Automapper can handle lists ... so that can be:
var clubsViewModelList = Mapper.Map<IList<Club>, IList<ClubViewModel>>(clubsList);
Unpack the view model
I know this is what you said you didn't want to do .. but you really should. It will make your mapping much simpler between your domain and your view. I am all for keeping them separate for many many reasons, but I think you're not doing yourself any favours by trying to flatten this sort of domain model into a single view model. So unpack it to multiple view models:
public class ClubViewModel
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public IList<CalendarEntryViewModel> CalendarEntries { get; set; } // <--- a list of them
}
Your map then becomes:
.ForMember(dest => dest.IsMonday, opt => opt.MapFrom(src => src.Day == Days.Monday))
.ForMember(dest => dest.IsTuesday, opt => opt.MapFrom(src => src.Day == Days.Tuesday))
/* etc... */
Then, remove all of the nullable dates and just represent this entry with two fields:
public DateTime? From { get; set; }
public DateTime? To { get; set; }
You can then order them by the day:
foreach (var club in clubsViewModelList) {
club.CalendarEntries = club.CalendarEntries.OrderBy(x => x.Day).ToList();
}
Then in your view .. just render them in order:
#foreach (var calendarEntry in club.CalendarEntries) {
Day: #calendarEntry.Day.ToString()
From: #calendarEntry.From.ToString("D")
}
..etc.
When it comes time to create.. you just render your entries as required:
public class ClubCalendarEntryCreateViewModel {
/* other properties here */
public IList<CalendarEntryViewModel> Days { get; set; }
}
Then in your controller:
viewModel.CalendarEntries = new ClubCalendarEntryCreateViewModel {
Days = new List<CalendarEntryViewModel>() {
new CalendarEntryViewModel {
IsMonday = true
},
new CalendarEntryViewModel {
IsTuesday = true
},
/* etc... */
}
};
Then you just render those and accept it back in your POST method. Your mapping on that method becomes:
club.CalendarEntries = Mapper.Map<IList<CalendarEntryViewModel>, IList<CalendarEntry>>(viewModel.CalendarEntries.Days);
I left out a little bit here (like how you would remodel your CalendarEntry domain object) .. hopefully you can fill in the blanks and understand what I mean. I know that seems like a lot of work - but you will be better for it later on. Your mappings won't be your enemy like they are now (they shouldn't ever be your enemy) and your code will become much clearer.
I hope that helps .. its probably not what you want to hear, but its definitely how I would do it (or similar, given more requirements).

Categories

Resources