How to conditionally map from two sources to a destination in AutoMapper - automapper

I have a PITA legacy DB model. Here is the relevant portion
class SalesOrder
{
public Recipient OrderBy {get;set;}
public Recipient BillTo {get;set;}
public List<SalesOrderLine> Lines {get;set;}
}
class SalesOrderLine
{
public Recipient ShipTo {get;set;}
public Address ShipToAddress {get;set;}
}
class Recipient
{
public Address DefaultAddress {get;set;}
}
Now comes the fun part.
class RecipientDTO {
public string Name {get;set;}
public string Address1 {get;set;}
public string Address2 {get;set;}
...
}
I have the OrderDTO that needs to be like this
new OrderDTO {
OrderBy = new RecipientDTO {
Name = OrderBy.Name,
Address1 = OrderBy.DefaultAddress.Addr1,
....
}
BillTo = new RecipientDTO {
Name = BillTo.Name,
Address1 = BilLTo.DefaultAddress.Addr1,
....
}
Lines = Lines.Select (l => new SalesOrderLineDTO {
ShipTo = new RecipientDTO {
Name = ShipTo.Name,
Address1 = l.ShipToAddress.Addr1, //NOTE. THIS IS NOT USING DEFAULT ADDRESS
....
}
} )
}
How do I write the mapping configuration in Auto Mapper for this to make sure the projection emits the chosen columns. If I use CustomResolver, the projection is not emitting the sql and it goes to the DB every time i access the address. Mucho sad!

To anyone who is interested , i have a backreference to Recipient from Address. SO I was able to do this following
m.CreateMap<SalesOrder, OrderDTO>()
.ForMember(d => d.OrderBy, conf => conf.MapFrom(o => o.OrderBy.DefaultAddress))
.ForMember(d => d.BillTo, conf => conf.MapFrom(o => o.BillTo.DefaultAddress));
m.CreateMap<Address, RecipientDTO>()
.ForMember(r => r.CustomerRecipientId, conf => conf.MapFrom(ra => ra.Recipient.CustRecipId))
.ForMember(r => r.Address1, conf => conf.MapFrom(ra => ra.Addr1))
.ForMember(r => r.Address2, conf => conf.MapFrom(ra => ra.Addr2))
....
m.CreateMap<SalesOrderLine, OrderLineDTO>()
.ForMember(l => l.OrderQuantity, conf => conf.MapFrom(l => l.OrderQty ?? 0))
.ForMember(l => l.ShipTo, conf => conf.MapFrom(z => z.ShipToAddress));

Related

C# Automapper on the nested list

I've the following dtos and I'm trying to use automapper, but couldn't quite understand on how to do the same, as this is the first time I'm using automapper any help is really appreciated
string order {get;set;}
string orderType {get;set;}
List<OrderItem> Items {get;set;}
}
public class OrderItem{
string itemID {get;set;}
Price price {get;set;}
}
public class Price {
string total {get;set;}
string regular {get;set;}
}
public class request {
string order {get;set;}
string orderType {get;set;}
List<Item> Items {get;set;}
}
public class Item {
string itemID {get;set;}
string itemPrice {get;set;}
string regularPrice {get;set;}
}
I'm trying the following
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<request,requestDTO>()
.ForMember(dest => dest.order, act=>act.MapFrom(src => src.order))
.ForMember(dest => dest.orderType, act=>act.MapFrom(src => src.orderType))
})
How do I create mapping to map
**
src.item.itemID to dest.item.itemID
src.item.itemPrice to dest.item.price.total
src.item.regularPrice to dest.item.price.regular**
for all the items in my source array
Just create another map for Item and OrderItem as follows:
cfg.CreateMap<Item, OrderItem>()
.ForPath(dest => dest.price.total, act => act.MapFrom(src => src.itemPrice))
.ForPath(dest => dest.price.regular, act =>act.MapFrom(src => src.item.regularPrice));
Also note that the mapper <request,requestDTO> does not need any explicit ForMember call, as both objects have identical property names:
cfg.CreateMap<request,requestDTO>();

Conditionally ignore a field from the source if a condition is true on destination

I'm using Automapper to update a series of entities from a DTO. However, there are some properties that I do not want to update from the DTO, if certain conditions apply. For example, the entity has a date in the past.
Use the PreCondition option. Here's a simple example:
public class Source
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Dest
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime LastUpdated { get; set; }
}
The mapping of Name will only happen if the current year of LastUpdated is 2015:
Mapper.CreateMap<Source, Dest>()
.ForMember(d => d.Name, o => o.PreCondition((rc) => ((Dest) rc.DestinationValue).LastUpdated.Year == 2015))
.ForMember(d => d.LastUpdated, o => o.Ignore());
Mapper.AssertConfigurationIsValid();
In the code below, the "dest" object will retain the name "Larry":
var src = new Source {Name = "Bob", Age = 22};
var dest = new Dest {Name = "Larry", LastUpdated = new DateTime(2014, 10, 11)};
Mapper.Map<Source, Dest>(src, dest);
If you change the year for LastUpdated to 2015, the Name property gets updated to "Bob".

map to derived type of destination in AutoMapper

I want to specify a mapping so that all mappings from "Source" to "Destination" is returned as a derived class of "Destination"
[Test]
public void Map_SourceToDestinationAsDerivedType_ReturnsDerivedType()
{
// arrange
AutoMapper.Mapper.CreateMap<Source, Destination>()
.CreateAs<ActualDestination>() // psedu code
.ForMember(dst => dst.Transformed, opt => opt.ResolveUsing(src => src.Property));
var source = new Source{Property = "hi mom" };
// act
var destination = AutoMapper.Mapper.Map<Destination>(source);
// assert
Assert.That(destination, Is.InstanceOf<ActualDestination>());
}
public class Source
{
public string Property { get; set; }
}
public class Destination
{
}
public class ActualDestination : Destination
{
public string Transformed { get; set; }
}
This is not directly supported by Automapper
However the closest what you can get is to define a mapper for the Source, ActualDestination pair
AutoMapper.Mapper.CreateMap<Source, ActualDestination>()
.ForMember(dst => dst.Transformed, opt => opt.ResolveUsing(src => src.Property));
And then use the ConstructUsing option in the Source, Destination mapping to do the translation from the Source to the ActualDestination:
AutoMapper.Mapper.CreateMap<Source, Destination>()
.ConstructUsing((Source s) => AutoMapper.Mapper.Map<ActualDestination>(s));

Automapper, mapping multiple properties into 1 w/o ValueResolver

My current code is working:
map.ForMember(x => x.Address, m => m.ResolveUsing(l => {
var engine = new MappingEngine((IConfigurationProvider)cfg);
var adress = engine.Map<AddressDto>(l.ContactInfo);
engine.Map(l.Address, adress);
return adress;
}));
but I thought there might be another way, something like:
map.ForMember(x => x.Address, m => m.MapFrom(x => x.ContactInfo));
map.ForMember(x => x.Address, m => m.MapFrom(x => x.Address));
But the last ForMember seems to override the existing map.
I'm trying to combine Address and ContactInfo properties into a single object on ListingDto.Address.
void Main()
{
var map = Mapper.CreateMap<Listing, ListingDto>();
var cfg = Mapper.Configuration;
map.ForMember(x => x.Address, m => m.ResolveUsing(l => {
var engine = new MappingEngine((IConfigurationProvider)cfg);
var adress = engine.Map<AddressDto>(l.ContactInfo);
engine.Map(l.Address, adress);
return adress;
}));
Mapper.CreateMap<Address, AddressDto>()
.ForMember(x => x.Latitude, x => x.Ignore());
Mapper.CreateMap<ContactInfo, AddressDto>()
.ForMember(x => x.Street, x=> x.Ignore());
Mapper.Map<ListingDto>(new Listing{
Name="Foo",
Address = new Address{Street = "Street"},
ContactInfo = new ContactInfo{ Latitude = "latitude"}}).Dump();
}
// Define other methods and classes here
public class Listing{
public string Name { get; set; }
public Address Address { get; set; }
public ContactInfo ContactInfo {get;set;}
}
public class ContactInfo{
public string Latitude {get;set;}
}
public class Address{
public string Street {get;set;}
}
public class AddressDto{
public string Latitude {get;set;}
public string Street {get;set;}
}
public class ListingDto{
public string Name { get; set; }
public AddressDto Address {get;set;}
}
.Dump() <-- is from linqpad to output
Something like this should work:
.ForMember(x => x.Address, o => o.MapFrom(
s => new AddressDto {
Latitude = s.ContactInfo.Latitude,
Street = s.Address.Street }));

AutoMapper mapping properties with private setters

Is it possible to assign properties with private setters using AutoMapper?
AutoMapper allows now (I am not sure, since when) to map properties with private setters. It is using reflection for creating objects.
Example classes:
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
public class PersonDto
{
public string Fullname { get; private set; }
}
And mapping:
AutoMapper.Mapper.CreateMap<Person, PersonDto>()
.ForMember(dest => dest.Fullname, conf => conf.MapFrom(src => src.Name + " " + src.Surname));
var p = new Person()
{
Name = "John",
Surname = "Doe"
};
var pDto = AutoMapper.Mapper.Map<PersonDto>(p);
AutoMapper will map property with private setter with no problem. If you want to force encapsulation, you need to use IgnoreAllPropertiesWithAnInaccessibleSetter. With this option, all private properties (and other inaccessible) will be ignored.
AutoMapper.Mapper.CreateMap<Person, PersonDto>()
.ForMember(dest => dest.Fullname, conf => conf.MapFrom(src => src.Name + " " + src.Surname))
.IgnoreAllPropertiesWithAnInaccessibleSetter();
The problem will emerge, if you will use Silverlight. According to MSDN: https://msdn.microsoft.com/en-us/library/stfy7tfc(v=VS.95).aspx
In Silverlight, you cannot use reflection to access private types and members.
If you set the value for this properties in the constructor like this
public class RestrictedName
{
public RestrictedName(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class OpenName
{
public string Name { get; set; }
}
then you can use ConstructUsing like this
Mapper.CreateMap<OpenName, RestrictedName>()
.ConstructUsing(s => new RestrictedName(s.Name));
which works with this code
var openName = new OpenName {Name = "a"};
var restrictedName = Mapper.Map<OpenName, RestrictedName>(openName);
Assert.AreEqual(openName.Name, restrictedName.Name);
See #600 Private/internal destination properties.
Solution:
public class PrivateInternalProfile {
protected override Configure() {
ShouldMapField = fieldInfo => true;
ShouldMapProperty = propertyInfo => true;
CreateMap<User, UserDto>(); //etc
}
}

Resources