AutoMapper - Map source object to nested object - c#

How to map a single object to a nested object?
The below one is my entity.
class Employee
{
public string name { get; set; }
public string city_name { get; set; }
public string State_name { get; set; }
}
I want to map to
class Employee
{
public string Name;
public Location Location;
}
class Location
{
public string City;
public string State;
}
Please note that the property names are different. Can anyone help to map these using AutoMapper?

Solution 1: ForPath
1.1 Create Profile instance with inheriting from Profile and put the configuration in the constructor.
1.1.1 Using ForPath to map nested property.
public class EmployeeProfile : Profile
{
public EmployeeProfile()
{
CreateMap<Employee, EmployeeDto>()
.ForPath(dest => dest.Location.City, opt => opt.MapFrom(src => src.city_name))
.ForPath(dest => dest.Location.State, opt => opt.MapFrom(src => src.State_name));
}
}
1.2 Adding profile to mapper configuration
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<EmployeeProfile>();
// Note: Demo program to use this configuration rather with EmployeeProfile
/*
cfg.CreateMap<Employee, EmployeeDto>()
.ForPath(dest => dest.Location.City, opt => opt.MapFrom(src => src.city_name))
.ForPath(dest => dest.Location.State, opt => opt.MapFrom(src => src.State_name));
*/
});
IMapper mapper = config.CreateMapper();
var employee = new Employee{name = "Mark", city_name = "City A", State_name = "State A"};
var employeeDto = mapper.Map<Employee, EmployeeDto>(employee);
}
Sample Solution 1 on .Net Fiddle
Solution 2: AfterMap
2.1 Create Profile instance with inheriting from Profile and put the configuration in the constructor.
2.1.1 Using AfterMap to perform mapping nested property after map occurs.
public class EmployeeProfile : Profile
{
public EmployeeProfile()
{
CreateMap<Employee, EmployeeDto>()
.AfterMap((src, dest) => { dest.Location =
new Location {
City = src.city_name,
State = src.State_name
};
});
}
}
2.2 Adding profile to mapper configuration
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<EmployeeProfile>();
// Note: Demo program to use this configuration rather with EmployeeProfile
/*
cfg.CreateMap<Employee, EmployeeDto>()
.AfterMap((src, dest) => { dest.Location =
new Location {
City = src.city_name,
State = src.State_name
};
});
*/
});
IMapper mapper = config.CreateMapper();
var employee = new Employee{name = "Mark", city_name = "City A", State_name = "State A"};
var employeeDto = mapper.Map<Employee, EmployeeDto>(employee);
}
Sample Solution 2 on .Net Fiddle
References
ForPath
Profile Instances
Before and After Map

Related

AutoMapper - Map source list to a destination array

I am trying to map a source list to a destination array using AutoMapper.
Source classes
public class ReservationSource
{
public Travel TravelSource { get; set; }
public string SeqNo { get; set; }
}
public class Travel
{
public string TravelId { get; set; }
public ICollection<Trip> Trips { get; set; }
}
public class Trip
{
public string TrainNumber { get; set; }
public string Arrival { get; set; }
}
Destination classes
public class ReservationDestination
{
public Route[] TravelDest { get; set; }
public string SeqNumber { get; set; }
}
public class Route
{
public string SequNumber { get; set; }
public string RouteId { get; set; }
}
private static route[] GetRoutes(ICollection<Trip> trips)
{
List<route> routeList = new List<route>();
foreach (var trip trips)
{
var route = new route
{
SequNumber = trip.trainNumber
};
routeList.Add(route);
}
return routeList.ToArray();
}
Map configuration
cfg.CreateMap<ReservationSource, ReservationDestination>()
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.SeqNumber, o => o.MapFrom(src => SeqNo))
.ForPath(dest => dest.TravelDest, o => o.MapFrom(src => GetRoutes(src)));
});
This is what I have tried, here I would like to eliminate the GetRoutes method where I will do a manual map using a foreach loop. Is it possible to use any other way without a loop?
Add mapping for Trip and Route classes.
cfg.CreateMap<Trip, Route>()
.ForMember(dest => dest.SequNumber, o => o.MapFrom(src => src.TrainNumber));
Complete Mapping Configuration
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Trip, Route>()
.ForMember(dest => dest.SequNumber, o => o.MapFrom(src => src.TrainNumber));
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.SeqNumber, o => o.MapFrom(src => src.SeqNo))
.ForPath(dest => dest.TravelDest, o => o.MapFrom(src => src.TravelSource.Trips));
});
IMapper mapper = config.CreateMapper();
var source = new ReservationSource
{
SeqNo = "Seq001",
TravelSource = new Travel
{
TravelId = "1",
Trips = new List<Trip>
{
new Trip { TrainNumber = "A0001" },
new Trip { TrainNumber = "B0001" }
}
}
};
var destination = mapper.Map<ReservationSource, ReservationDestination>(source);
Console.WriteLine(JsonConvert.SerializeObject(destination));
Sample Program
Output
{"TravelDest":[{"SequNumber":"A0001","RouteId":null},{"SequNumber":"B0001","RouteId":null}],"SeqNumber":"Seq001"}
References
Lists and Arrays - AutoMapper documentation

C# AutoMapper - Set default value in dest property if not exist in source

I have:
Class A
{
public string FirstName { get; set; }
}
Class B
{
public string FirstName { get; set; }
public Guid RequestId { get; set; }
}
I want to map from A to B, since "A" doesn't have RequestId I want to set it to Guid.NewGuid()
I tried this code:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<A, B>().ForMember(m => m.RequestId, o => Guid.NewGuid());
});
_mapper = config.CreateMapper();
But I'm still getting empty Guid in RequestId.
That mapping should look like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<A, B>().ForMember(m => m.RequestId, o => o.MapFrom(s => Guid.NewGuid()));
});
Change mapping confugration:
cfg.CreateMapper<A,B>().ForMember(x => x.RequestId, o => o.NullSubstitute(Guid.NewGuid());

How to Map to abstract conditionally

I'd like to map a common object to an instance of an abstraction (concrete instance of the abstraction) without saying which concrete instance I want. I want to choose the concrete instance based on a condition which will be set up in mapping configuration.
Like here:
ConstructUsing(y => !string.IsNullOrEmpty(y.Label) ? (X)new XLabel() : new XValue())
and here:
m.Map<X>(i2); //where X is an abstraction
Data
// Source
public class Input
{
public int Id { get; set; }
public string Label { get; set; }
public decimal Amount { get; set; }
}
//Base target
public abstract class X
{
public int P1 { get; set; }
}
//Concrete target 1
public class XLabel : X
{
public string Name { get; set; }
}
//Concrete target 2
public class XValue : X
{
public string Value { get; set; }
}
CONFIGURATION:
//config
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Input, XLabel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Label));
cfg.CreateMap<Input, XValue>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Amount));
cfg.CreateMap<Input, X>()
.Include<Input, XLabel>()
.Include<Input, XValue>()
.ConstructUsing(y => !string.IsNullOrEmpty(y.Label) ? (X)new XLabel() : new XValue())
.ForMember(dest => dest.P1, opt => opt.MapFrom(src => src.Id));
});
MAPPING:
//Test
Input i1 = new Input { Id = 1, Amount = 2, };
Input i2 = new Input { Id = 1, Label = "my label" };
IMapper m = new Mapper(config);
var result1 = m.Map<X>(i1);
var result2 = m.Map<X>(i2);
Notice that I'm doing this:
var result1 = m.Map<X>(i1); not this var result1 = m.Map<XValue>(i1); -And this is what I wanted to do -I wanted to Map to abstraction not to a concrete instance
TESTS:
Assert.IsType<XValue>(result1); //PASSED
Assert.IsType<XLabel>(result2); //PASSED
Assert.Equal("my label", ((XLabel)result1).Name);// Message: Assert.Equal() FAILURE Expected: my label; Actual: (null)
Problem:
Looks like this mapping does not work:
cfg.CreateMap<Input, XLabel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Label));
cfg.CreateMap<Input, XValue>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Amount));
Message: Assert.Equal() FAILURE Expected: my label; Actual: (null)

Automapper - reuse of mapping rules for projection

Given 2 source entities:
class SourceA
{
public string Info1 { get; set; }
public string Info2 { get; set; }
}
class SourceB
{
public A A { get; set; }
public string OptionalExtraInfo { get; set; }
}
and one destination class:
class Dest
{
public string ModifiedInfo1 { get; set; }
public string ModifiedInfo2 { get; set; }
public string ModifiedOptionalExtraInfo { get; set; }
}
I want to have the following code working with EF6:
var destsFromA = dbContext.SourcesA.ProjectTo<Dest>().ToArray();
var destsFromB = dbContext.SourcesB.ProjectTo<Dest>().ToArray();
So I define Automapper.net mappings:
SourceA => Dest
SourceB => Dest
with custom rules on how to project Info1 into ModifiedInfo1, and Info2=>ModifiedInfo2:
CreateMap<SourceA, Dest>()
.ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.Info1 + " something else-1")
.ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.Info1 + " something else-2")
.ForMember(x => ModifiedOptionalExtraInfo, opt => opt.Ignore());
CreateMap<SourceB, Dest>()
.ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.A.Info1 + " something else-1")
.ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.A.Info2 + " something else-2")
.ForMember(x => ModifiedOptionalExtraInfo, opt => opt.MapFrom(src => src.OptionalExtraInfo + " something else-3"));
How do I reuse mapping rules for ModifiedInfo1, ModifiedInfo2 in second mapping as they are the same as in the first case?
UPDATE In my certain case I figured out how to reuse SourceA => Dest mapping in a natural way.
First, I added a reverse-reference (navigation property) SourceA.B as these entities are really in one-to-zero-or-one relationship and EF has to know about that.
Then I changed the aggregation root in my application code and it became:
var destsFromA = dbContext.SourcesA.ProjectTo<Dest>().ToArray();
var destsFromB = dbContext.SourcesB.Select(x => x.A).ProjectTo<Dest>().ToArray();
so I only had to work with the only SourceA => Dest mapping
Finally I changed the mapping itself:
CreateMap<SourceA, Dest>()
.ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.Info1 + " something else-1")
.ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.Info1 + " something else-2")
.ForMember(x => ModifiedOptionalExtraInfo, opt => opt.MapFrom(src => src.B ? src.B.OptionalExtraInfo + " something else-3" : null);
As this is a solution to a problem, but not an answer to the original question, I accepted Ilya Chumakov's answer as a correct one.
Parametrize mappings with expressions:
opt.MapFrom(expression)
.ForMember(x => x.Foo, expression)
It's easy to extract these expression variables with ReSharper, so it could look like:
Expression<Func<SourceA, string>> expression = src => src.Info1 + " something else-1";
var func = expression.Compile();
cfg.CreateMap<SourceA, Dest>()
.ForMember(x => x.ModifiedInfo1,
opt => opt.MapFrom(expression));
cfg.CreateMap<SourceB, Dest>()
.ForMember(x => x.ModifiedInfo1,
opt => opt.MapFrom(src => func(src.A)));
Update: In case of LINQ to SQL translation, the solution becomes much more complicated. expression.Compile() won't work and a new expression should be created:
Expression<Func<SourceA, string>> expression = src => src.Info1 + "foo";
//it should contain `src => src.A.Info1 + "foo"`
var newExpression = ConvertExpression(expression);
Basic implementation with ExpressionVisitor:
private static Expression<Func<SourceB, string>>
ConvertExpression(Expression<Func<SourceA, string>> expression)
{
var newParam = Expression.Parameter(typeof(SourceB), "src");
var newExpression = Expression.Lambda<Func<SourceB, string>>(
new ReplaceVisitor().Modify(expression.Body, newParam), newParam);
return newExpression;
}
class ReplaceVisitor : ExpressionVisitor
{
private ParameterExpression parameter;
public Expression Modify(Expression expression, ParameterExpression parameter)
{
this.parameter = parameter;
return Visit(expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda<Func<SourceB, bool>>(
Visit(node.Body),
Expression.Parameter(typeof(SourceB)));
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(SourceA))
{
return Expression.Property(parameter, nameof(SourceB.A));
}
throw new InvalidOperationException();
}
}
A quick and easy solution would be to use an intermediate class.
First the classes who are used then later on in the posting
public class SourceA
{
public string A { get; set; }
}
public class SourceB
{
public string B { get; set; }
}
public class Dest
{
public string ValueFromSourceA { get; set; }
public string ValueFromSourceB { get; set; }
}
That said here is the intermediate class:
public class Intermediate
{
public SourceA SourceA { get; set; } = new SourceA();
public SourceB SourceB { get; set; } = new SourceB();
}
Now let's start sticking the parts together with Automapper.
Defining profile classes
public class DestinationProfile : Profile
{
public DestinationProfile()
{
this.CreateMap<Intermediate, Dest>()
.ForMember(destination => destination.ValueFromSourceA,
opt => opt.MapFrom(src => src.SourceA.A))
.ForMember(destination => destination.ValueFromSourceB,
opt => opt.MapFrom(src => src.SourceB.B));
}
}
public class IntermediateProfile : Profile
{
public IntermediateProfile()
{
this.CreateMap<Intermediate, Dest>()
.ForMember(destination => destination.ValueFromSourceA, map => map.MapFrom(src => src.SourceA.A))
.ForMember(destination => destination.ValueFromSourceB, map => map.MapFrom(src => src.SourceB.B));
// ----- TODO: Create mapping for source classes.
}
}
And here comes the heavy lifting for the mapping we marked as todo above.
You can use IValueResolver interface from Automapper to define value mapping.
So in our case the resolvers look like
public class SourceAResolver : IValueResolver<SourceA, Intermediate, SourceA>
{
public SourceA Resolve(SourceA source, Intermediate destination, SourceA destMember, ResolutionContext context)
{
return source;
}
}
public class SourceBResolver : IValueResolver<SourceB, Intermediate, SourceB>
{
public SourceB Resolve(SourceB source, Intermediate destination, SourceB destMember, ResolutionContext context)
{
return source;
}
}
With the above now we are able to replace the todo statement
this.CreateMap<SourceA, Intermediate>()
.ForMember(destination => destination.SourceA, map => map.ResolveUsing<SourceAResolver>());
this.CreateMap<SourceB, Intermediate>()
.ForMember(destination => destination.SourceB, map => map.ResolveUsing<SourceBResolver>());
Finally we register our profile classes to the Automapper
public static class AutomapperProfile
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<DestinationProfile>();
cfg.AddProfile<IntermediateProfile>();
});
}
}
Firing up a console with the below code snippet helps testing our stuff
AutomapperProfile.Configure();
var a = new SourceA {A = "Value A"};
var b = new SourceB() {B = "Value B"};
var intermediate = new Intermediate() {SourceA = a, SourceB = b};
var destination = AutoMapper.Mapper.Map<Dest>(intermediate);
Console.WriteLine(destination.ValueFromSourceA);
Console.Read();
Done!
Note: The code snippets provided are just coded to demo the usage/meaning of what is meant by "intermediate" class - have not implemented the way back to the source classes.
Have fun with it :)

AutoMapper Ignore on child collection property

I am trying to map object's of the same type which have a collection of child objects and am finding that Ignore() applied to properties on the child object seem to be umm... ignored!
Here's a unit test which demonstrates the problem.
class A
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<B> Children { get; set; }
}
class B
{
public int Id { get; set; }
public string Name { get; set; }
}
[TestClass]
public class UnitTest1
{
[TestInitialize()]
public void Initialize()
{
Mapper.CreateMap<A, A>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
Mapper.CreateMap<B, B>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
}
[TestMethod]
public void TestMethod1()
{
A src = new A { Id = 0, Name = "Source", Children = new List<B> { new B { Id = 0, Name = "Child Src" } } };
A dest = new A { Id = 1, Name = "Dest", Children = new List<B> { new B { Id = 11, Name = "Child Dest" } } };
Mapper.Map(src, dest);
}
After the Map call the A object's Id property is still 1, as expected, but child B object's Id property is changed from 11 to 0.
Why?
There are several bugs in AutoMapper 4.1.1.
First is about UseDestinationValue: https://github.com/AutoMapper/AutoMapper/issues/568
Second is about nested collections: https://github.com/AutoMapper/AutoMapper/issues/934
Horrifying! The workaround is to map your B instances directly:
Mapper.CreateMap<A, A>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.Children, opt => opt.Ignore());
Mapper.CreateMap<B, B>()
.ForMember(dest => dest.Id, opt => opt.Condition((ResolutionContext src) => false));
and add additional mapping calls:
Mapper.Map(src, dest);
Mapper.Map(src.Children.First(), dest.Children.First()); //example!!!
You may call Mapper.Map in cycle:
for (int i = 0; i < src.Children.Count; i++)
{
var srcChild = src.Children[i];
var destChild = dest.Children[i];
Mapper.Map(srcChild, destChild);
}
This will make things work right.

Categories

Resources