Automapper and class hierarchy - c#

Given the following sources:
public class SourceBase { public string TheString { get; set; } }
public class SourceDerived : SourceBase { }
and destinations:
public class DestBase { public string MyString { get; set; } }
public class DestDerived : DestBase { }
And this mapping:
CreateMap<SourceBase, DestBase>()
.ForMember(dest => dest.MyString, o => o.MapFrom(x => x.TheString))
.Include<SourceDerived, DestDerived>();
CreateMap<SourceDerived, DestDerived>();
Mapper.AssertConfigurationIsValid(); // Exception is thrown here
However, this gives a mapping error saying MyString isn't mapped on DestDerived. What gives? Do I really need to repeat the mappings for base class properties in all derived types (I do have more than one subclass in my actual code).
EDIT:
The exact exception is The following 1 properties on DestDerived could not be mapped: MyString. Add a custom mapping expression, ignore, or rename the property on DestDerived.

Please check this post:
http://groups.google.com/group/automapper-users/browse_thread/thread/69ba514a521e9599
It works fine if you declare it like in the code below (using AutoMapper 1.1.0.188). I am not sure if this solves your problem.
var result = Mapper.CreateMap<SourceBase, DestBase>()
.ForMember(dest => dest.MyString, o => o.MapFrom(x => x.TheString));
//.Include<SourceDerived, DestDerived>();
Mapper.CreateMap<SourceDerived, DestDerived>();
var source = new SourceDerived();
var destDerived = new DestDerived();
source.TheString = "teststring";
var mapResult = Mapper.Map<SourceBase, DestBase>(source, destDerived).MyString;
Console.WriteLine(mapResult);

Related

How to map a list of objects to an object that contains multiple lists?

I am using automapper 9.0.0.
My situation is as follows. I have a list of items which are all instances of an abstract base class, let's call it BaseClass. There are 2 classes that inherit that class, let's call those Bar1Class and Bar2Class.
I want to map a list of BaseClass to an object that contains 2 lists. One list is a DTO for the Bar1Class objects from the list, and the 2nd one is for the Bar2Class objects from the list:
List<BaseClass> items = GetItems();
var dto = Mapper.Map<FooResponseModel>(items);
The hierarchy is as follows:
// Response models
public class FooResponseModel
{
public IEnumerable<Bar1Model> Bar1Models {get;set;}
public IEnumerable<Bar2Model> Bar2Models {get;set;}
}
public class Bar1Model
{
public string MyString {get;set;}
public int MyInt {get;set;}
}
public class Bar2Model
{
public string MyString {get;set;}
public bool MyBool {get;set;}
}
public abstract class BaseClass
{
public string MyString {get;set;}
}
public class Bar1Class : BaseClass
{
public int MyInt {get;set;}
}
public class Bar2Class : BaseClass
{
public bool MyBool {get;set;}
}
How would I set this up?
Using the CreateMap<BaseClass, FooResponseModel>() doesn't really work because I can't divide the collections. Doing something like CreateMap<Bar1Class, Bar1Model>() would allow me to map the classes itself, but not allow me to set up the lists.
Thanks!
Edit:
I would map it by hand like this now, because i dont know how to map the upper object correctly. I would of course add CreateMap<Bar1Class, Bar1Model>() and such beforehand.
var dto = new FooResponseModel
{
Bar1Models = items
.Where(x => x is Bar1Class)
.Cast<Bar1Class>()
.Select(x => Mapper.Map<Bar1Model>()),
Bar2Models = items.
.Where(x => x is Bar2Class)
.Cast<Bar2Class>()
.Select(x => Mapper.Map<Bar2Model>())
}
Create two maps between source classes and destination classes. Then, add mapping from a sequence of BaseClass to FooResponseModel and point AutoMapper how to populate Bar1Models and Bar2Models properties.
CreateMap<Bar1Class, Bar1Model>();
CreateMap<Bar2Class, Bar2Model>();
CreateMap<IEnumerable<BaseClass>, FooResponseModel>()
.ForMember(d => d.Bar1Models, o => o.MapFrom(s => s.Where(b => b is Bar1Class)))
.ForMember(d => d.Bar2Models, o => o.MapFrom(s => s.Where(b => b is Bar2Class)));

How to map an ICollection<T> to Class derived from List<T>

I am setting up a mapping between my models and my view models and I'm trying to map from an ICollection to class that derives from List
I have tried to make a mapping between my ListItemClassVM and ICollection but get an error 'Argument types do not match'
Option one mapping works with this:
public class ParentVM
{
public List<ListItemClass> ListItemClasses { get; set; }
}
Option two mapping not working:
public class ParentVM
{
public ListItemClassVM ListItemClasses { get; set; }
}
public ListItemClassVM : List<ListItemClass>
{
}
Mapping Setup:
public ModelClass_ParentVM_Profile()
{
CreateMap<ModelClass, ParentVM>()
.ForMember(d => d.ListItemClasses, o => o.MapFrom(i => i.ModelCollection))
;
CreateMap<ParentVM, ModelClass>()
;
}
trying to setup the mapping so option two will map.
I think that there are more way to reach the solution, but you can't escape from a manual transposition from ICollection< ListItemClass > to ListItemClassVM.
The simplier way maybe is to add to your ListItemClassVM a constructor that accepts an ICollection< ListItemClass > and initialize itself with the elements in ICollection, then you could do something like:
CreateMap<ModelClass, ParentVM>()
.ForMember(d => d.ListItemClasses, o => o.MapFrom(i =>new ListItemClassVM (i.ModelCollection)))
;

Automapper - exclude some objects from mapped collection

I have the following map rules:
CreateMap<ViewModels.ApplicationDriverAccidentFormVM, ApplicationDriverAccidentDomain>();
then I want to map ViewModels.ApplicationDriverFormVM to ApplicationDriverDomain, both are have Accidents property, which are appropriate collections for each type.
public class ApplicationDriverDomain
{
public List<ApplicationDriverAccidentDomain> Accidents { get; set; }
}
public class ApplicationDriverFormVM
{
public List<ApplicationDriverAccidentFormVM> Accidents { get; set; }
}
And I want to exclude (not map) all records, which are not satisfied some conditions
I try to write the following code:
CreateMap<ViewModels.ApplicationDriverFormVM, ApplicationDriverDomain>()
.ForMember(dest => dest.Accidents, opt => opt.MapFrom(src => GetNotNullFromCollection(src.Accidents)))
where GetNotNullFromCollection is:
List<object> GetNotNullFromCollection(object input)
{
List<object> output = new List<object>();
foreach (var item in (List<object>)input)
{
if (!Utils.IsAllNull(item))
output.Add(item);
}
return output;
}
but it says me:
Unable to cast object of type
'System.Collections.Generic.List1[Web.ViewModels.ApplicationDriverAccidentFormVM]'
to type 'System.Collections.Generic.List1[System.Object]'.
Why and how to do it?
your method GetNotNullFromCollection receives an object but you are passing it a list.
Anyway, I would recommend using Generics instead of objects.
I solved it by the following way:
CreateMap<ViewModels.ApplicationDriverFormVM, ApplicationDriverDomain>().ForMember(dest => dest.Accidents, opt => opt.MapFrom(src => src.Accidents.Where(o => !Utils.IsAllNull(o))))

How to configure a mapping of collection types with AutoMapper?

For example, I've implemented two classes like these:
public class A
{
public List<C> Items { get; set; }
}
public class B
{
public IImmutableList<C> Items { get; set; }
}
public class C
{
}
When I try to map A to B and vice versa, I get an exception because List<string> cannot be converted to IImmutable<string>.
Probably I could provide a mapping for A<->B, but since it'll be a very common pattern in my solution, I'd like to avoid to manually mapping each class that may fall into the same case.
Is there anyway I can generalize the whole mapping using generic type definitions from a collection type to another collection type?
This is what I want to avoid
mapperConfig.CreateMap<A, B>()
.ForMember(a => a.Items, opts => opts.Ignore())
.AfterMap
(
(source, target) =>
{
target.Items = source.Items.ToImmutableList();
}
);

How to tell AutoMapper to use "pass by reference?"

By default automapper creates a new object based on the destination's type:
public void Doit( Person personMissingStuff )
{
PersonTemplate template = _personDao.GetPersonTemplate(1);
Mapper.CreateMap<PersonTemplate, Person>();
Person basePerson = Mapper.Map<Person>( template );
Mapper.CreateMap<Person, Person>();
Person completePerson =
Mapper.Map<Person, Person>( basePerson, personMissingStuff );
...
}
Instead of getting a completePerson I just get a basePerson again. How do I tell AutoMapper to run the mappings by reference instead of by value?
Mapper.Map(source, dest) actually returns the destination object, in your case it'll be personMissingStuff.
With that said, assuming that you want to fill in only the null properties in the destination, you need to configure the mapping properly, and not map when the destination property has value.
The following sample does exactly this for class properties. For value properties, probably you need to do additional configuration. The example uses NUnit and SharpTestsEx:
[TestFixture]
public class LoadIntoInstance
{
public class Template
{
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
public string OtherData { get; set; }
}
[Test]
public void Should_load_into_instance()
{
Mapper.CreateMap<Template, Person>()
.ForMember(d=>d.OtherData, opt=>opt.Ignore());
Mapper.CreateMap<Person, Person>()
.ForAllMembers(opt=>opt.Condition(ctx=>ctx.DestinationValue==null));
Mapper.AssertConfigurationIsValid();
var template = new Template {Name = "template"};
var basePerson = Mapper.Map<Person>(template);
var noNamePerson = new Person {OtherData = "other"};
var result = Mapper.Map(basePerson, noNamePerson);
result.Should().Be.SameInstanceAs(noNamePerson);
result.Satisfy(r =>
r.Name == "template" &&
r.OtherData == "other");
}
}
Just use traditional shallow cloning...
Person completePerson = basePerson.MemberwiseClone();
This should keep the reference types and clone the value types.
MSDN Link

Categories