delegating Member mapping to child object with AutoMapper - c#

I have a destination class that combines properties from a source class and an inner class of that source class.
class Source {
public int Id {get;set;}
public int UseThisInt {get;set;}
public InnerType Inner {get;set;}
// other properties that the Destination class is not interested in
}
class InnerType {
public int Id {get;set;}
public int Height {get;set;}
// more inner properties
}
my destination class should combine UseThisInt and all properties of the InnerType.
class Destination {
public int Id {get;set;}
public int UseThisInt {get;set;}
public int Height {get;set;}
// more inner properties that should map to InnerType
}
Now my AutoMapper configuration looks like this:
CreatMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.Height, o => o.MapFrom(s => s.Inner.Height));
AutoMapper will correctly map UseThisInt between Source and Destination, but I would like to be able to let it map all the other properties in Destination like Height without an explicit ForMember configuration.
I tried using
Mapper.Initialize(cfg => cfg.CreateMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.UseThisInt, o => o.MapFrom(s => s.UseThisInt))
.ForAllOtherMembers(o => o.MapFrom(source=> source.Inner))
);
, but that did not achieve the intended result and left Destination.Height untouched.

Most examples of AutoMapper demonstrate creating a new Destination object from some source object, but AutoMapper can also be used to update an existing object taking those properties from the source object that are mapped and leaving any remaining properties untouched.
Consequently it is possible to map from the source to the destination in multiple steps.
So if you create a mapping configuration from InnerType like so:-
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>();
cfg.CreateMap<InnerType, Destination>();
});
Then you can make use of this ability to overlay mappings by mapping into the destination object twice.
var dest = Mapper.Map<Destination>(src);
Mapper.Map(src.Inner, dest);
One downside to this approach is that you need to be mindful of this when using the Mapper to generate a Destination object. However, you have the option of declaring this second mapping step within your AutoMapper configuration as an AfterMap instruction.
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.AfterMap((src, dest) => Mapper.Map(src.Inner, dest));
cfg.CreateMap<InnerType, Destination>();
});
With this updated configuration you can perform the mapping with a single Map call:-
var dest = Mapper.Map<Destination>(src);

CreateMap<Source, Destination>()
.AfterMap((src, dest) =>
{
dest.Height = src.Inner.Height;
});

Related

Automapper - map list of complex object to list of properties

I have following DomainObject class:
public class MyDomainObj
{
public CUSTOMER customer {get;set;} // This is database entity
public ORDER order {get;set;}
}
My DTO looks like this:
public class MyDTO
{
public string custId{get;set;}
public strinf orderId{get;set;}
}
Let's say in CUSTOMER table I have an attribute with the name: customer_id
same for ORDER table its order_id
Here is my automapper configuration:
m.CreateMap<CUSTOMER, MyDTO>().ForMember(d => d.custId, o => o.MapFrom(s => s.customer_id));
m.CreateMap<ORDER, MyDTO>().ForMember(d => d.orderId, o => o.MapFrom(s => s.order_id));
I wrote an extension method for mapper in order to work:
public static class ExtensionAutoMapper
{
public static TDestination Map<TSource, TDestination>(this TDestination destination, TSource source)
{
return Mapper.Map(source, destination);
}
}
Usage is:
var response = Mapper.Map<MyDTO>(myDomainObj.customer)
.Map(myDomainObj.order);
This works fine.
Question:
How do I change my mapping configuration in order to map list of domain objects to list of dto?
something along these lines
var response = Mapper.Map<List<MyDomainObj>, List<MyDTO>>(myDomainObj);
Edit:
I would like to map fields in database entity to dto properties automatically if they have the same name.
Answer provided by #jmoerdyk, solve my problem. However, in that approach, I have to map all fields of a database entity to dto even if they have the same name.
You just provide the Mapping from MyDomainObj to MyDto, and it should be able to handle mapping the collections:
Mapper.CreateMap<MyDomainObj,MyDTO>()
.ForMember(d => d.custId, o => o.MapFrom(s => s.customer.customer_id))
.ForMember(d => d.orderId, o => o.MapFrom(s => s.order.order_id));
Then call it just like you had (assuming myDomainObj is a List<MyDominObj>):
var response = Mapper.Map<List<MyDomainObj>, List<MyDTO>>(myDomainObjList);

3rd Level Automapper Relationship

I'm integrating with a 3rd party API which is returning a complex data structure and in a part of it I have the following relationship.
public class Parent{
public List<SmartLink> SmartLink { get; set; }
}
The SmartLink object looks like below:
public class SmartLink {
public Address AddressInfo { get; set; }
}
I have tried to map it in several ways, one of them below, but I still get a null on the AddressInfo object.
cfg.CreateMap<Address, AddressInfo>();
cfg.CreateMap<Source, Parent>()
//This is not allowed since Automapper cannot map to 2nd level
.ForMember(d => d.SmartLink.AddressInfo, map => map.MapFrom(src => src.Smartlink.ToList().Select(addr => addr.Address)));
The line below works perfectly:
.ForMember(d => d.SmartLink, map => map.MapFrom(s => s.Smartlink.ToList()))
How can I map/flatten a 3rd level property with Automapper, any pointers?
I had overthought on it. I simply added the following mapping and it worked.
cfg.CreateMap<Address, AddressInfo>();
cfg.CreateMap<SmartlinkPart, SmartLink>(MemberList.Destination)
.ForMember(d => d.AddressInfo, map => map.MapFrom(s => s.Address));
The idea is that for the member AddressInfo, the first line above will provide it's mapping instruction.

Automapper conditional map from multiple source fields

I've got a source class like the following:
public class Source
{
public Field[] Fields { get; set; }
public Result[] Results { get; set; }
}
And have a destination class like:
public class Destination
{
public Value[] Values { get; set; }
}
So I want to map from EITHER Fields or Results to Values depending on which one is not null (only one will ever have a value).
I tried the following map:
CreateMap<Fields, Values>();
CreateMap<Results, Values>();
CreateMap<Source, Destination>()
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Fields != null);
opt.MapFrom(s => s.Fields });
})
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Results != null);
opt.MapFrom(s => s.Results);
});
Only issue with this is that it looks if the last .ForMember map doesn't meet the condition it wipes out the mapping result from the first map.
I also thought about doing it as a conditional operator:
opt => opt.MapFrom(s => s.Fields != null ? s.Fields : s.Results)
But obviously they are different types so don't compile.
How can I map to a single property from source properties of different types based on a condition?
Thanks
There is a ResolveUsing() method that allows you for more complex binding and you can use a IValueResolver or a Func. Something like this:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.ResolveUsing<ConditionalSourceValueResolver>());
And the value resolver depending on your needs may look like:
public class ConditionalSourceValueResolver : IValueResolver<Source, Destination, Value[]>
{
public Value[] Resolve(Source source, Destination destination, Value[] destMember, ResolutionContext context)
{
if (source.Fields == null)
return context.Mapper.Map<Value[]>(source.Results);
else
return context.Mapper.Map<Value[]>(source.Fields);
}
}
Following #animalito_maquina answer.
Here is an update for 8.0 Upgrade:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.MapFrom<ConditionalSourceValueResolver>());
And to save you time, ValueResolvers are not supported for Queryable Extensions
ResolveUsing is not available, try this.
It's working for me
CreateMap<Source, Destination>()
.ForMember(opt => opt.value, map =>
map.MapFrom((s, Ariel) => s.Fields != null ? s.Fields : s.Results));

Automapper. Map if source member is null

I have two classes and map one to other with Automapper. For instance:
public class Source
{
// IdName is a simple class containing two fields: Id (int) and Name (string)
public IdName Type { get; set; }
public int TypeId {get; set; }
// another members
}
public class Destination
{
// IdNameDest is a simple class such as IdName
public IdNameDest Type { get; set; }
// another members
}
Then I use Automapper to map Source to Destination:
cfg.CreateMap<Source, Destination>();
It works properly but sometimes member Type in class Source becomes null. In these cases I would like to map member Type in class Destination from TypeId property. That's what I want in a nutshel:
if Source.Type != null
then map Destination.Type from it
else map it as
Destination.Type = new IdNameDest { Id = Source.Id }
Is it possible with AutoMapper?
You can use the .ForMember() method while declaring the mapping.
Like so :
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type != null ? src.Type : new IdNameDest { Id = src.Id }));
While LeeeonTMs answer works fine AutoMapper provides a specialised mechanism to substitute null values. It "allows you to supply an alternate value for a destination member if the source value is null anywhere along the member chain" (taken from the AutoMapper manual).
Example:
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt => opt.NullSubstitute(new IdNameDest { Id = src.Id }));
With C# 6.0, the null-coalescing operator can be used.
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type ?? new IdNameDest { Id = src.Id }));
I managed to resolve it with Mapping Resolvers
public class SomeResolver : IValueResolver<Soruce, Dest, Guid>
{
public Guid Resolve(Source source, Dest destination, Guid, destMember, ResolutionContext context)
{
destination.Value= source.Value!=null ? source.Value:0;
return destination.MainGuid = Guid.NewGuid();
}
}
and then on mapping configuraiton
CreateMap<BioTimeEmployeeSummaryDTO, BioTimeEmployeeAttendanceSummary>()
.ForMember(dest => dest.MainGuid, opt => opt.MapFrom<YourResolverClass>())
.ReverseMap();

Dynamically passing properties in Select for projection in Entity Framework

Is there a way to pass the properties I want to retrieve from the DB in a Select dynamically, I don't know the properties I need beforehand and I don't want to write the conditions in my repository.
I don't want to retrieve all the fields at once, just the ones I need based on some conditions.
For example:
public class Student
{
public string Property1 {get; set;}
public string Property2 {get; set;}
//other properties here
[NotMapped]
public string Selected
{
if(condition)
return Property1;
else
return Property2;
}
}
and in the service layer I have
query.Select(s => new StudentViewModel
{
Value = s.Selected; //this one will get the property we want based on a condition
//other stuff here
//OtherValue = s.OtherProperty
}
).FirstOrDefault();
Selector
An easy but ugly way is to use a Selector:
query.Select(Selector()).FirstOrDefault();
And the Selector can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if (Condition())
return x => new StudentViewModel
{
Name = x.Property1,
OtherName = x.OtherName
};
return x => new StudentViewModel
{
Name = x.Property2,
OtherName = x.OtherName
};
}
As you can see the obviously downside here is that you need to copy/paste all other selected properties. That is why its ugly.
AutoMapper
Configs
You can use AutoMapper with different configurations. First you need to define a standard mapping for all properties that don't need to be dynamic.
public static void AddStandardStudentMap(this IMappingExpression<Student, StudentViewModel> map)
{
map.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherProperty))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherProperty2));
// you can concat .ForMember() for each property you need.
}
Next, you need to define the different configs and add the AddStandardStudentMap method to each invidual mapping.
var config1 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property1))
.AddStandardStudentMap()
);
var config2 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property2))
.AddStandardStudentMap()
);
After this, just use your conditions to decide which config do you need
IConfigurationProvider provider;
if(Condition())
provider = config1;
else
provider = config2;
And then instead of .Select() use:
query.ProjectTo<StudentViewModel>(provider).FirstOrDefault();
As we can see this solution is still ugly and has a lot of overhead but its needed in some cases, thats why i stated it here.
Expression
This is a bit similar to the Configs but brings you more flexibility and less writing effort.
First create a config but this time with a Selector
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherName))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherName2))
// and so on. Map all your properties that are not dynamically.
// and then the Selector
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => Selector()))
);
The Selector method can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if(Condition())
return src => src.Property1;
else
return src => src.Property2;
}
And then just use it like the configs solution but without selecting a particular config:
query.ProjectTo<StudentViewModel>(config).FirstOrDefault();
Conclusion
I know this is a lot input and there are even more possibilities to achieve the behaviour that you want, with or without AutoMapper. But i would suggest you (grounded on the information you gave us) to use AutoMapper with Expressions. It should give the flexibility you need and is extensible for further requirements.

Categories

Resources