I'm using AutoMapper, and I declared a mapping rule as follows:
CreateMap<Foo, Bar>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.ActivityName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Name, opt => opt.MapFrom<NameResolver>())
.ForMember(dest => dest.HistName, opt => opt.MapFrom<HistNameResolver>())
.ForMember(dest => dest.Asset, opt => opt.MapFrom<AssetResolver>())
.ForMember(dest => dest.Props, opt => opt.MapFrom<PropsResolver>())
I'm writing unit tests for this rule, but I have trouble understanding the order by which resolvers are invoked (order of calling their Resolve() method).
I wrote a unit test for NameResolver and thought it would be the first resolver whose Resolve() method would be called. While debugging I discovered I was wrong. The first resolver invoked was AssetResolver.
"Oh fine! I guess resolvers are taken care of alphabetically." I thought. I was wrong again. Debugging showed that the order resolvers were invoked through their Resolve() method was:
AssetResolver -> NameResolver -> HistNameResolver -> PropsResolver
AssetResolver moved to the front of the queue, while the remaining resolvers were "processed" in the order they were declared on the mapping rule. I don't understand why they are invoked in this order. Can anyone shed some light on this? Can I change this order in any way? Thank you!
You can configure this by calling SetMappingOrder(int).
For example:
CreateMap<Foo, Bar>()
.ForMember(dest => dest.Name, opt =>
{
opt.MapFrom<NameResolver>();
opt.SetMappingOrder(5);
}
Although I would also consider Lucian Bargaoanu's comment about using a type converter.
Related
Is it possible to resolve a few destination properties from one source property using AutoMapper?
The obvious way to go is to do like this:
CreateMap<Source, Destination>
.ForMember(d => d.dest1, opt => opt.MapFrom(s => s.sour["dest1"]))
.ForMember(d => d.dest2, opt => opt.MapFrom(s => s.sour["dest2"]))
...
.ForMember(d => d.dest10, opt => opt.MapFrom(s => s.sour["dest10"]))
But is there maybe something like a custom resolver which would allow to write a method which would perform the assignment of multiple destination properties using one (or maybe even a few) source property (properties)?
I just started to use automapper to map DTOs<->Entities and it seems to be working great.
In some special cases I want to map only some properties and perform additional checks. Without automapper the code looks like this (using fasterflect's PropertyExtensions):
object target;
object source;
string[] changedPropertyNames = { };
foreach (var changedPropertyName in changedPropertyNames)
{
var newValue = source.GetPropertyValue(changedPropertyName);
target.SetPropertyValue(changedPropertyName, newValue);
}
Of course this code won't work if type conversions are required. Automapper uses built-in TypeConverters and I also created some specific TypeConverter implementations.
Now I wonder whether it is possible to map individual properties and use automapper's type conversion implementation, something like this
Mapper.Map(source, target, changedPropertyName);
Update
I think more information is necessary:
I already created some maps, e.g.
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
and I also created a map with a custom typeconverter for the nullable dateTime property in CalendarEvent, e.g.
Mapper.CreateMap<DateTimeOffset?, DateTime?>().ConvertUsing<NullableDateTimeOffsetConverter>();
I use these maps in a web api OData Controller. When posting new EntityDTOs, I use
Mapper.Map(entityDto, entity);
and save the entity to a datastore.
But if using PATCH, a Delta<TDto> entityDto is passed to my controller methods. Therefore I need to call entityDto.GetChangedPropertyNames() and update my existing persistent entity with the changed values.
Basically this is working with my simple solution, but if one of the changed properties is e.g. a DateTimeOffset? I would like to use my NullableDateTimeOffsetConverter.
If you just want to map only some select property than you have to do as below
// Create a map
var map = CreateMap<Source,Target>();
// ingnore all existing binding of property
map.ForAllMembers(opt => opt.Ignore());
// than map property as following
map.ForMember(dest => dest.prop1, opt => opt.MapFrom( src => src.prop1));
map.ForMember(dest => dest.prop2, opt => opt.MapFrom( src => src.prop2));
You can make some projection using MapFrom method - http://automapper.readthedocs.io/en/latest/Projection.html
Mapper.Map(source, target)
.ForMember(m => m.Property, opt => opt.MapFrom(src => src.ChangedProperty));
For example (reffering to AutoMapper documentation):
// Model
var calendarEvent = new CalendarEvent
{
Date = new DateTime(2008, 12, 15, 20, 30, 0),
Title = "Company Holiday Party"
};
// Configure AutoMapper
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute));
To do this with the latest version of AutoMapper first you should map the properties that you want then ignore the rest.
CreateMap<TSource, TDestination>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
.ForMember(dest => dest.Prop, opt => opt.MapFrom(src => src.AnotherProp))
// ...
.ForAllOtherMembers(opt => opt.Ignore()); // <=== Ignore The rest
Otherwise if you do map.ForAllMembers(opt => opt.Ignore()); first, it will ignore all mappings even the mappings after this.
If I read your question correctly, yes, as long as your destination (target) property matches your conversion.
So if I am going from a string to a bool for a Status of "A" or "I" (active/inactive), I can do something like:
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status == "A"))
And then when going the other direction, convert it back:
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status ? "A" : "I"))
A date example:
.ForMember(dest => dest.SomeDate, opt => opt.MapFrom(src => src.SomeDate.ToString("M/d/yyyy")));
Here is the CreateMap method:
Mapper.CreateMap<Domain.Models.Organization, OrganizationInputModel>()
.ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Address.Address1))
.ForMember(dest => dest.Address2, opts => opts.MapFrom(src => src.Address.Address2))
.ForMember(dest => dest.City, opts => opts.MapFrom(src => src.Address.City))
.ForMember(dest => dest.State, opts => opts.MapFrom(src => src.Address.State))
.ForMember(dest => dest.Zip, opts => opts.MapFrom(src => src.Address.Zip))
.ReverseMap();
Here Address is a complex object.
I was expecting to see the bi-directional model mapping. But it turns out the complex object mapping is broken:
var entity = AutoMapper.Mapper.Map<Domain.Models.Organization>(model);
I got an error:
{"message":"Null value for non-nullable member. Member: 'Address'."}
Some SO posts says reverse mapping is only for simple object, and we will have to create two Mapper.CreateMap in this case. Is it really so?
Yes, you need to create the reverse mapping in this case. The reason for this is because AutoMapper wouldn't know how to instantiate the target Address property.
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.
I am trying to use Entity Framework and have Automapper update my entity from my contract.
My code looks like this:
var temp = OrderContract;
Order order = dataAccess.FindOne<Order>(x => x.OrderId == temp.OrderId)
?? new Order();
Mapper.Map(OrderContract, order);
if (order.OrderId <= 0)
dataAccess.Add(order);
(Note: I am using the Repository Pattern. dataAccess.FindOne calls CreateQuery to return one entity.)
The problem I am having is with the relationships. I get this error when I do an update (inserts work just fine):
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I am guessing that automapper is not updating the way I want it to. From the error message and googling around I have surmised that my relationships that are collections (and maybe even the ones that are not collections) are being recreated by Automapper.
How can I tell Automapper to just update and not remake any objects or collections?
Guesses:
I read that maybe I need to use the UseDestinationValue option for automapper. I went back and put that on all my collections But when I do that then my inserts fail with a foreign key violation.
Code Mappings:
Using UseDestinationValue only on one collection (this one inserts but will not update):
//+ Source
Mapper.CreateMap<SourceContract, Source>()
.IgnoreAllNonExisting();
//+ SelectedRequirement
Mapper.CreateMap<SelectedRequirementContract, SelectedRequirement>()
.ForMember(x => x.SelectedRequirementId, opt => opt.MapFrom(src => src.RequirementId))
.IgnoreAllNonExisting();
//+ Comment Contract
Mapper.CreateMap<CommentContract, Comment>()
.ForMember(x => x.CommentText, opt => opt.MapFrom(src => src.Comment))
.IgnoreAllNonExisting();
//+ Order Automapper setup
Mapper.CreateMap<OrderContract, Order>()
.ForMember(x => x.Source, opt => opt.MapFrom(src => src.Source))
.ForMember(x => x.Comment, opt => opt.MapFrom(src => src.Comment))
//Although a mapping was created for Comment entity,
//we still need to map the CommentId of the Order entity otherwise it will remain null during an update.
//Another way to handle this would be to Delete CommentId from the Order entity.
//However, if anyone updates (Update from model) OrderDataModel.edmx that property would show up again thus causing
//a null value to be inserted during an update.
.ForMember(x => x.CommentId, opt => opt.MapFrom(src => src.Comment.CommentId))
.ForMember(x => x.SelectedRequirements, opt => {opt.UseDestinationValue(); opt.MapFrom(src => src.Requirements);})
.ForMember(x => x.OrderStateId, opt => opt.MapFrom(src => src.StateId))
.ForMember(x => x.OrderStateId, opt => opt.MapFrom(src => src.StateId))
.IgnoreAllNonExisting();
Using UseDestinationValue everywhere (this one does not insert):
//+ Source
Mapper.CreateMap<SourceContract, Source>()
.IgnoreAllNonExisting();
//+ SelectedRequirement
Mapper.CreateMap<SelectedRequirementContract, SelectedRequirement>()
.ForMember(x => x.SelectedRequirementId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.RequirementId); })
.IgnoreAllNonExisting();
//+ Comment Contract
Mapper.CreateMap<CommentContract, Comment>()
.ForMember(x => x.CommentText, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment); })
.IgnoreAllNonExisting();
//+ Order Automapper setup
Mapper.CreateMap<OrderContract, Order>()
.ForMember(x => x.Source, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Source); })
.ForMember(x => x.Comment, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment); })
//Although a mapping was created for Comment entity,
//we still need to map the CommentId of the Order entity otherwise it will remain null during an update.
//Another way to handle this would be to Delete CommentId from the Order entity.
//However, if anyone updates (Update from model) OrderDataModel.edmx that property would show up again thus causing
//a null value to be inserted during an update.
.ForMember(x => x.CommentId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Comment.CommentId); })
.ForMember(x => x.SelectedRequirements, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.Requirements); })
.ForMember(x => x.OrderStateId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.StateId); })
.ForMember(x => x.OrderStateId, opt => { opt.UseDestinationValue(); opt.MapFrom(src => src.StateId); })
.IgnoreAllNonExisting();
What do I need to so I can insert and update?
I think what you want is for AutoMapper to not create your EF entity and take the one you sent it. In version2.0 of auto mapper there is an overload to map method where you can basically pass your datacontext delegate and let EF create your object.
Please look at this article.