Automapper: How do I map Lazy<TFrom> to TTo? - c#

I need to map a list of Lazy<TFrom> to a list of TTo. What I've done isn't working - I only get null values. Without the Lazy<> wrapper it works perfectly. What am I doing wrong?
// Map this list
// It's passed in as an argument and has all the data
IList<Lazy<Employee>> employees
public static IList<TTo> MapList(IList<Lazy<TFrom>> fromModel)
{
Mapper.CreateMap<Lazy<TFrom>, TTo>();// Is this needed?
return Mapper.Map<IList<Lazy<TFrom>>, IList<TTo>>(fromModel);
// This doesn't work
// return Mapper.Map<IList<TTo>>(fromModel.Select(x => x.Value));
}
// Maps the child classes
Mapper.CreateMap<Lazy<Employee>, EmployeeDTO>()
.ForMember(x => x.AccidentDTO, i => i.MapFrom(model => model.Value.GetAccident()))
.ForMember(x => x.CriticalIllnessDTO, i => i.MapFrom(model => model.Value.GetCriticalIllness()))
.ForMember(x => x.ValidationMessages, i => i.MapFrom(model => model.Value.ValidationMessages));
// Returns nulls !!!
var dataDTO = MyMapper<Lazy<Employee>, EmployeeDTO>.MapList(employees);

Here's what I've done to solve my problem. If someone has a better idea, please let me know and I'll mark it as the answer.
// Get the Values of the Lazy<Employee> items and use the result for mapping
var nonLazyEmployees = employees.Select(i => i.Value).ToList();
var dataDTO = MyMapper<Employee, EmployeeDTO>.MapList(nonLazyEmployees);
public static IList<TTo> MapList(IList<Lazy<TFrom>> fromModel)
{
Mapper.CreateMap<Lazy<TFrom>, TTo>();
return Mapper.Map<IList<Lazy<TFrom>>, IList<TTo>>(fromModel);
}
// Maps the child classes
Mapper.CreateMap<Employee, EmployeeDTO>()
.ForMember(x => x.AccidentDTO, i => i.MapFrom(model => model.GetAccident()))
.ForMember(x => x.CriticalIllnessDTO, i => i.MapFrom(model => model.GetCriticalIllness()))
.ForMember(x => x.ValidationMessages, i => i.MapFrom(model => model.ValidationMessages));

Related

AutoMapper - Is it possible to access the source object in AddTransform?

I'd like to check if a property is still null after a generic ResolveUsing using a IMemberValueResolver. I tried AfterMap but it did not apply after a ResolveUsing on a collection, so I thought that AddTransform might be more appropriate.
Based on the existing AutoMapper unit tests, I can see that it is possible to override the destination property:
p.CreateMap<Source, Dest>()
.ForMember(d => d.Value, opt => opt.AddTransform(d => d + ", seriously"));
Would it be possible to get access to the source object inside AddTransform to extract a value from that object to override the destination?
This is what I'm basically trying to do:
cfg.CreateMap<Foo, Bar>()
.ForMember(d => d.Description, opts =>
{
opts.ResolveUsing<LocalizeResolver, ResourceType>(src => src.ResourceType);
opts.AddTransform(d => "whatever"); // src.Description??
})
.AfterMap((src, dst) => {
// this never works
if (string.IsNullOrWhiteSpace(dst.Description))
{
dst.Description = src.Description;
}
});

automapper optimization mapping (model to view model -> get latest version)

I am trying to optimize this part of code:
Mapper.CreateMap<Document, DocumentViewModel>()
.ForMember(g => g.Id, map => map.MapFrom(d => d.documentVersion.Where(v => v.version == d.documentVersion.Select(s => s.version).Max()).OrderByDescending(s => s.subVersion).First().Id))
.ForMember(g => g.IdRootDocument, map => map.MapFrom(d => d.Id))
.ForMember(g => g.certyficateType, map => map.MapFrom(d => d.documentVersion.Where(v => v.version == d.documentVersion.Select(s => s.version).Max()).OrderByDescending(s => s.subVersion).First().certyficateType))
I'm Using automapper, and I'm trying to optimize this part of code
In this part I'm trying mapping object from document to documentViewModel, in this complex model, source data always will be latest document version:
d => d.documentVersion.Where(v => v.version == d.documentVersion.Select(s => s.version).Max()).OrderByDescending(s => s.subVersion).First().myProportyX
Could anyone offer an example or suggestion as to how to approach optimization in this situation?
d => d.documentVersion.Where(v => v.version == d.documentVersion.Select(s => s.version).Max()).OrderByDescending(s => s.subVersion).First().myProporty
You are iterating quite a few times here, you may consider doing something like:
d.documentVersion.OrderByDescending(v => v.version).ThenByDescending(v => v.subVersion).First().myProperty
to reduce the number of iterations and only get the top version/subversion.
optimization mapping:
Mapper
.CreateMap<Document, DocumentViewModel>()
.ConvertUsing(doc =>
{
DocumentViewModel result = new DocumentViewModel();
DocumentVersion lastVersion = doc.documentVersion.Where(v => v.version == doc.documentVersion.Select(s => s.version).Max()).OrderByDescending(s => s.subVersion).First();
Mapper.Map(lastVersion, result);
return result;
});

AutoMapper.Mapper.Map sporadically returns object with null properties

I am using Automapper in my MVC/EF application. Occasionally when I launch my application to debug (F5), AutoMapper.Mapper.Map returns a mapped object that has null property values. I need to restart for this to resolve itself. This issue also occurs when I deploy my application to a remote server. As a workaround I have to check for null properties and then manually map my object.
ProductHierarchyRow mappedObj = _mapProvider.Map<VW_PROD_HIERARCHY,ProductHierarchyRow>(foundRow);
if (String.IsNullOrEmpty(mappedObj.DepartmentId)) //Test one of the properties
{
//Manually map because Automapper has failed for some reason
mappedObj = new ProductHierarchyRow();
mappedObj.MarketChannel = foundRow.MARKETCHANNEL;
mappedObj.BrandId = foundRow.BRAND_ID;
mappedObj.BrandLabel = foundRow.BRAND_LABEL;
mappedObj.DivisionId = foundRow.DIVISION_ID;
mappedObj.DivisionLabel = foundRow.DIVISION_LABEL;
mappedObj.DepartmentId = foundRow.DEPARTMENT_ID;
mappedObj.DepartmentLabel = foundRow.DEPARTMENT_LABEL;
mappedObj.ClassId = foundRow.CLASS_ID;
mappedObj.ClassLabel = foundRow.CLASS_LABEL;
mappedObj.SubclassId = foundRow.SUBCLASS_ID;
mappedObj.SubclassLabel = foundRow.SUBCLASS_LABEL;
}
Here is the mapping interface/implementation class:
public class MappingProvider : IMappingProvider
{
public MappingProvider()
{
AutoMapper.Mapper.CreateMap<VW_PROD_HIERARCHY, ProductHierarchyRow>().ForAllMembers(opt => opt.Ignore());
AutoMapper.Mapper.CreateMap<VW_PROD_HIERARCHY, ProductHierarchyRow>()
.ForMember(x => x.MarketChannel, o => o.ResolveUsing(s => s.MARKETCHANNEL))
.ForMember(x => x.BrandId, o => o.ResolveUsing(s => s.BRAND_ID))
.ForMember(x => x.BrandLabel, o => o.ResolveUsing(s => s.BRAND_LABEL))
.ForMember(x => x.DivisionId, o => o.ResolveUsing(s => s.DIVISION_ID))
.ForMember(x => x.DivisionLabel, o => o.ResolveUsing(s => s.DIVISION_LABEL))
.ForMember(x => x.DepartmentId, o => o.ResolveUsing(s => s.DEPARTMENT_ID))
.ForMember(x => x.DepartmentLabel, o => o.ResolveUsing(s => s.DEPARTMENT_LABEL))
.ForMember(x => x.ClassId, o => o.ResolveUsing(s => s.CLASS_ID))
.ForMember(x => x.ClassLabel, o => o.ResolveUsing(s => s.CLASS_LABEL))
.ForMember(x => x.SubclassId, o => o.ResolveUsing(s => s.SUBCLASS_ID))
.ForMember(x => x.SubclassLabel, o => o.ResolveUsing(s => s.SUBCLASS_LABEL));
}
public T1 Map<T, T1>(T entry)
{
return AutoMapper.Mapper.Map<T, T1>(entry);
}
public T1 Map<T, T1>(T source, T1 dest)
{
return (T1)AutoMapper.Mapper.Map(source, dest, typeof(T), typeof(T1));
}
}
Any ideas on why this is happening? Tips of how to troubleshoot? Thanks in advance!
Those CreateMaps should only be called once per AppDomain, at startup. Make sure those are called at App_Start. What's happening is you have competing threads calling CreateMap and Map at the same time.

MapFrom not always mapping properly

Here's a piece of simple code:
var map = Mapper.CreateMap<Contact, ContactDtoGrid>().
ForMember("executive_account", x => x.MapFrom(y => y.executive_account.Id)).
ForMember("executive_account_Name", x => x.MapFrom(y => y.executive_account.Name));
foreach (Contact c in allContacts)
{
string accountNumber = accounts.Where(x=>x.Key==c.executive_account.Id).SingleOrDefault().Value;
string protocolDescription = (protocolDefinitions.Where(x => x.ProtocolOptionSetValue == c.executive_protocol).Any()) ? protocolDefinitions.Where(x => x.ProtocolOptionSetValue == c.executive_protocol).SingleOrDefault().PortalDescription : string.Empty;
if (protocolDescription != string.Empty)
Console.Write(protocolDescription); //I do get an output so it's not always empty
map.ForMember("executive_account_Number", x => x.MapFrom(y => accountNumber));
map.ForMember("executive_protocol_desc", x => x.MapFrom(y => protocolDescription));
var contactDto = Mapper.Map<Contact, ContactDtoGrid>(c);
}
So all properties map properly, except for executive_protocol_desc which is always an empty string. Like I mentionned in the comment above, this is the case regardless whether protocolDescription is an empty string or not.
Am I doing something obviously wrong ?
Thanks.
Inside the method, you're not really doing a MapFrom, but setting a value. Since automapper is using nested closures, it's just easier to use a direct assignment:
...
var contactDto = Mapper.Map<Contact, ContactDtoGrid>(c);
contactDto.executive_protocol_desc = protocolDescription;
If you need to keep it in the mapper, you should be able to just put the protocol magic as the expression. This would allow you to pull the protocol description out of the foreach loop. Note: I also simplified the linq expression.
map.ForMember("executive_protocol_desc", opt => opt.MapFrom(src =>
protocolDefinitions.Any(pdef => pdef.ProtocolOptionSetValue == src.executive_protocol))
? protocolDefinitions.SingleOrDefault(pdef => pdef.ProtocolOptionSetValue == src.executive_protocol).PortalDescription
: string.Empty)
I took those lines out of the loop:
map.ForMember("executive_account_Number", x => x.MapFrom(y => accountNumber));
map.ForMember("executive_protocol_desc", x => x.MapFrom(y => protocolDescription));
And before the loop kicks in, I changed it to this:
map.ForMember(dest => dest.executive_protocol_desc, opt => opt.MapFrom(x => protocolDefinitions.Where(y => y.ProtocolOptionSetValue == x.executive_protocol.Value).SingleOrDefault().PortalDescription));
map.ForMember(dest => dest.executive_account_Number, opt => opt.MapFrom(x => accounts.Where(y => y.Key == x.executive_account.Id).SingleOrDefault().Value));
Now this maps properly.

Am I using Automapper 2.0's Include functionality correctly?

Either I'm not, or it isn't working... I have a single Source class that I want to map to multiple views that inherit from each other.
Basically the base class is the Detail, and the child class is Edit or Update which use all the same data as Detail, plus a couple other fields to manage their own lists or whatever.
Here are the maps I'm using:
Mapper.CreateMap<Ticket, Detail>()
.Include<Ticket, Update>()
.Include<Ticket, Edit>()
.ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.Priority.Code))
.ForMember(dest => dest.TicketID, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusCode))
.ForMember(dest => dest.Category, opt => opt.MapFrom(src => src.ProblemCategoryCode))
.ForMember(dest => dest.crmBusCode, opt => opt.MapFrom(src => src.Company.crmBusCode))
.ForMember(dest => dest.TeamMembers, opt => opt.MapFrom(src => src.Schedules.Where(s => s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Update>()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules.Where(s => s.EmployeeID == Util.CurrentUserID() && s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>();
Then if I Mapper.Map(ticket) any of the properties that use MapFrom don't get evaluated, they just end up with the values they'd have had if there was no set mapping.
So what's wrong here?
As an alternative solution if you don't want to call Mapper.Map two times. You can move the common mappings of Detail into an extension method:
public static class MappingExtensions
{
public static IMappingExpression<Ticket, TDest> MapDetailProperties<TDest>(
this IMappingExpression<Ticket, TDest> mapBase) where TDest : Detail
{
return mapBase
.ForMember(dest => dest.Priority,
opt => opt.MapFrom(src => src.Priority.Code))
///....
.ForMember(dest => dest.TeamMembers,
opt => opt.MapFrom(src => src
.Schedules.Where(s => s.CompleteTime == null)));
}
}
And then use that extension method when registering the Ticket -> Update and Ticket -> Edit mappers:
Mapper.CreateMap<Ticket, Update>()
.MapDetailProperties()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules
.Where(s => s.EmployeeID == Util.CurrentUserID() &&
s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>()
.MapDetailProperties();
Then you can use Map normally:
Ticket ticket = new Ticket();
var edit = Mapper.Map<Ticket, Edit>(ticket);
var update = Mapper.Map<Ticket, Update>(ticket);
Am I using Automapper 2.0's Include functionality correctly?
No--When you use .Include, AutoMapper expects that the destination classes are in a similar hierarchy as the source classes (This is discussed further here). In other words, if you were mapping to different subclasses of Ticket to Detail, Update and Edit, Include would be appropriate.
This doesn't seem helpful in your case. I would recommend using the overload of .Map that takes an existing object and modifies it. That way, you only have to define a mapping for the base type:
Ticket ticket = new Ticket();
Edit edit = new Edit();
Mapper.Map<Ticket, Detail>(ticket, edit);
// Edit has now been automapped using the base mapping.
Mapper.Map<Ticket, Edit>(ticket, edit);
// The properties unique to Edit have now been mapped.

Categories

Resources