AutoMapper non-destructive list reconciliation? - c#

I have a list on the target object that I want to reconcile against the source.
The list is already created, I just want AutoMapper to add/update/delete items from this list, at no time should the list be set to a new object reference. How can I do this?
The reason for this is that the target object is an ORM generated code, and for whatever reason it has no setter.
The target looks like this:
public class Target
{
//This is the target member.
public List EntityList
{
get{
return _entityList;
}
}
//Except this is a much more complicated, track-able list
private List _entityList= new List();
}

From my experience, AutoMapper isn't really designed with mapping onto already populated objects in mind. But one way to do what you're asking is to configure AutoMapper to ignore the EntityList property for mapping, then use AfterMap to call some custom reconciliation function you write to align the two lists:
Mapper.CreateMap<Source, Target>()
.ForMember(dest => dest.EntityList, opt => opt.Ignore())
.AfterMap((src, dest) => Reconcile(src.EntityList, dest.EntityList));

Related

How can I ignore list members that are null? [duplicate]

I have a domain model that contains a collection and I want to use AutoMapper to map the parent and children to the view model but I don't want children that have been "soft" deleted to be taken across. For instance:
public class Customer {
public EntitySet<Order> {get;set;}
}
public class Order {
public DateTime? DeletedDate {get;set;}
}
my AutoMapper definition would be
Mapper.CreateMap<Customer, CustomerViewModel>();
Mapper.CreateMap<Order, OrderViewModel>();
and I don't want Orders to be in the view model that have a value for DeletedDate.
Is that possible in AutoMapper? Many thanks in advance,
Steve.
I came across similar issue and finally the approach similar to the one below worked for me:
Mapper.CreateMap<Customer, CustomerViewModel>()
.ForMember(dest => dest.Orders,
opt => opt.MapFrom(src => src.Orders.Where(o => !o.DeletedDate.HasValue)));
This assumes your Customer entity and CustomerViewModel dto have collections named "Orders".
This sounds like it would be a good fit for a custom ValueResolver. It will allow you to do your logic checks in an isolated fashion. I don't have Visual Studio in front of me right now, but I can add some sample code later if you'd like.
EDIT:
After tinkering with this I don't think a ValueResolver is the way to go. I was able to get it to work by using the following conditional configuration for the Order mapping:
Mapper.CreateMap<Order, OrderViewModel>()
.ForAllMembers(opt => opt.Condition(src => !src.DeletedDate.HasValue));
The only thing with this is that theOrderViewModel will still come over but it will be null. In other words if you had 3 orders, and one had a deletion date, then the number of orders you will have in your view model will still be 3, but the deleted value will be null. I'm guessing it would be best to just have 2, but I'm not seeing a clear way to do that right now.
Here's a post with a response from the author of AutoMapper that talks about a Skip method, but I wasn't able to see that feature in the latest release that I'm using.

Left right DTO to object AutoMapper- C#

We recently implemented automapper and currently looking to optimise assigning values from the DTO to model. currently we are doing something like
model.Property1 = dto.Property1;
model.SomePropertyType = dto.PropertyType;
model.Property2 = dto.Property2;
Now this could go pretty long and repetitive task to all Mapper classes.
Is there a way to simplify this on AutoMapper?
If you are using Automapper then have you not defined the maps (profile)? I believe, you might have defined those so please use those to instruct Automapper how to map source object to Target.
Another point Automapper also works based on naming convention, so if you have same property name in both source and target then it will automap automatically. So you don't unnecessary define the mapping fort it. To override mapping for a property (or whose name/type does not match), you can use .ForMember method.
cfg.CreateMap<MyDTO, MyModel>()
.ForMember(destination => destination.PropertyType,
opts => opts.MapFrom(source => source.SomePropertyType ));
You can read about Automapper at here.
Now in the code to get the mapped object, use it like
Mapper.Map<MyModel>(object of MyDTO);

Creating maps for members using Automapper

I'm beginning AutoMapper and had a question. I came across sample code like this:
Automapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
So this would let me convert a book to a book model, and then map the src to the src.author.name property because there was not a 1-1 mapping.
To confirm, it does not work in reverse on its own, meaning I now need to explicitly do this:
Mapper.Mapper.CreateMap<BookViewModel, Book>()
.ForMember(dest => dest.Author.Name,
opts => opts.MapFrom(src => src.Author));
Is that correct? So to further confirm, if I had 50 view models and views, I'd literally need 100 (one for the way in, one for the way out, plus any additional lines of code in the .ForMember expression)
Is this true? Is this a common practice (i.e. you could potentially see hundreds of lines of code to handle the field mapping back and forth for multiple DTOs with properties that do not match up 1-1)?
CreateMap creates a mapping for AutoMapper to use later when required. This is required only once in the lifetime of your Application. In order to make use of this mapping you have defined, you just need to call Automapper.Map method. Thus you don't have to create the mapping again and again.
Example:
var myBookViewModel = Automapper.Map<Book, BookViewModel>(myBook);
You are correct saying that defining a mapper for Book to BookViewModel will not make Automapper to create a mapper for BookViewModel to Book automatically for you. You have to create the mapper.
In essence yes, you'd need basically to specify both back and forth configurations to handle each DTO mapping. Without a deterministic pattern it is impossible to guess how the mapping would work between complex object when members' names are different, or when the properties you want are further within other objects, like in your example.
However, Automapper is fairly smart if you follow some basic patterns. In your case, if you change the DTO property name to AuthorName instead of Author, the CreateMap will properly map without having to specify the mapping manually (they call this "Flattening"). Automapper will even map methods to properties if they follow a GetProperty() => Property { get; set; } pattern. Renaming your DTO properties to follow this pattern could probably save you a bunch of lines. Take a look at their starters guide for other ways to simplify your mappings.

AutoMapper, Call Mapper.Map() inside a custom type converter?

I am currently testing with AutoMapper, but i currently have a case where the property names do not match each other, so a custom type convert was needed.
But when i use the custom type converter, i have to map all other properties manually? i can't call another Map inside the type converter ofcourse as this will cause a overflow.
This is unwanted as there are at most 3 model specific properties that do not match per model so i DO want the other properties to be automaticaly mapped.
Could anyone point me in the right direction for this one?
You don't need to use a custom type converter to map classes where there a few properties that simply have names that don't match. Custom type converters are for when you need to, as they say, "take complete control over the conversion of one type to another".
Set up the map with CreateMap() and then set some extra rules using ForMember(), like this:
Mapper.CreateMap<Person, Customer>()
.ForMember(dest => dest.Surname, opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.DateOfBirth, opt => opt.MapFrom(src => src.DOB));
This maps LastName and DOB from the source Person class to the Surname and DateOfBirth properties of the destination Customer class.

Automapper many-to-many stackoverflowexception

I'm getting a stack overflow for the following mapping:
Mapper.CreateMap<Parent, ParentViewModel>()
.ForMember(x => x.Children, o => o.MapFrom(x => x.Children.ConvertToChildrenViewModel()));
Mapper.CreateMap<Children, ChildrenViewModel>()
.ForMember(x => x.Parents, o => o.MapFrom(x => x.Parents.ConvertToParentViewModel()));
I understand why this is happening, clearly an infinite loop here. How am I supposed to get this to work in automapper? I need parents to know about their children and their children to know about their parents. Would I have to create another ViewModel for Children.Parents that doesn't contain the Parents.Children property?
Extension methods example, similarly for children:
public static IList<ParentViewModel> ConvertToParentViewModel(this IEnumerable<Parent> parents)
{
return Mapper.Map<IList<ParentViewModel>>(parents);
}
There's a MaxDepth setting you can use for recursive mappings. I've never used it before, but it may help you out. You set it on the type mappings:
Mapper.CreateMap(...).MaxDepth(5)
AutoMapper does keep track of what's mapped, but only in the context of a single Map call, not multiple external calls to Mapper.Map.
You shouldn't need the ForMember piece on either mapping configuration. If you remove that, AutoMapper will traverse the parent/child relationships and keep track of what has already been mapped.

Categories