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")));
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'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;
}
});
I'm trying to map two collections by using EF's navigation property.
Collection.Items is a List<Item>
CollectionDTO has a navigation property to a cross-join table called CollectionItem, which has another navigation property to Item.
I want each CollectionDTO.CollectionItem.Item to map to Collection.Item.
I have tried this but I can't figure it out.
Can someone help?
var mapperConfig = new MapperConfiguration(cfg =>
{
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Where(x => x.CollectionId == src.Id).ToList().ForEach(ci => ci.Item)));
});
You can use Select extension method like this:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Select(ci=>ci.Item).ToList()));
If Item navigation property is a collection, then use SelectMany extension method:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.SelectMany(ci=>ci.Item).ToList()));
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.