I have defined a mapping from one type to DTO. Another type references the first type as an property, but the output should be an flattened DTO that should use the already defined mapping for the first type.
class Program {
static void Main(string[] args) {
var mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<FirstDataType,
FirstTypeDto>().ForMember(d => d.TypeResult, opt => opt.MapFrom(s => s.ToString()));
/* HOW TO CONFIGURE MAPPING OF THE 'FirstData' PROPERTY TO USE THE ABOVE DEFINED MAPPING
cfg.CreateMap<SecondDataType, SecondTypeDto>()
*/
});
var firstData = new FirstDataType {
TypeName = "TestType",
TypeValue = "TestValue"
};
var secondData = new SecondDataType {
Id = 1,
Name = "Second type",
FirstData = firstData
};
var mapper = mapperConfiguration.CreateMapper();
var firstDto = mapper.Map<FirstTypeDto>(firstData);
var secondDto = mapper.Map<SecondTypeDto>(secondData);
Console.ReadKey(true);
}
}
public class FirstDataType {
public string TypeName {
get;
set;
}
public string TypeValue {
get;
set;
}
public override string ToString() {
return $ "{TypeName}: {TypeValue}";
}
}
public class SecondDataType {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
public FirstDataType FirstData {
get;
set;
}
}
public class FirstTypeDto {
public string TypeName {
get;
set;
}
public string TypeValue {
get;
set;
}
public string TypeResult {
get;
set;
}
}
public class SecondTypeDto: FirstTypeDto {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
}
How should I configure mapping for the second type to use the defined mapping for the property 'FirstData'?
Thanks!
First, credit goes to Lucian Bargaoanu for leading me in the right direction.
Basically, you need to create a mapping from the source to the destination derived type, but just include the existing mapping.
cfg.CreateMap<FirstDataType, SecondTypeDto>()
.IncludeBase<FirstDataType, FirstTypeDto>()
.ReverseMap();
cfg.CreateMap<SecondDataType, SecondTypeDto>()
.IncludeMembers(s => s.FirstData)
.ReverseMap();
Related
Let's say I have the following objects:
Source:
public class Scores()
{
public int Id { get; set; }
public string Name { get; set; }
public decimal ScoreA { get; set; }
public decimal ScoreB { get; set; }
public decimal ScoreC { get; set; }
}
Destination:
public class ScoresDto()
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Score> ScoreDetails { get; set; }
}
public class Score()
{
public string Name { get; set; }
public decimal Value { get; set; }
}
I understand how I can map straight 1 to 1 property mapping like this:
public AutomapperProfiles()
{
CreateMap<Scores, ScoresDto>()
.ForMember(x => x.Id, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name))
}
However, how would I map as part of the above mapping ScoreA, ScoreB and ScoreC into individual new Score objects, where 'Name' is the name of the property and Value is the decimal value, and then have those scores make up the ScoreDetails list on ScoresDto?
You can create a custom resolver to do the job;
public class ScoreDetailsResolver : IValueResolver<Scores, ScoresDto, IList<Score>>
{
public IList<Score> Resolve(Scores source, ScoresDto destination, IList<Score> member, ResolutionContext context)
{
destination = destination ?? new ScoresDto();
destination.ScoreDetails = destination.ScoreDetails ?? new List<Score>();
destination.ScoreDetails.Add(new Score { Name = source.Name, Value = source.ScoreA });
destination.ScoreDetails.Add(new Score { Name = source.Name, Value = source.ScoreB });
destination.ScoreDetails.Add(new Score { Name = source.Name, Value = source.ScoreC });
return destination.ScoreDetails;
}
}
And use it like this:
CreateMap<Scores, ScoresDto>()
.ForMember(d => d.ScoreDetails, o => o.MapFrom<ScoreDetailsResolver>());
No need to map other properties as they will be mapped automatically.
I have the following Entity-Models
public class Blog
{
public int Id { get; set;}
public string Title { get; set; }
public string Body { get; set; }
[ForeignKey("Category")]
public int? CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Category
{
public int Id { get; set;}
public string Name { get; set; }
}
public class Comment
{
public int Id { get; set;}
public string Title { get; set; }
public string Body { get; set; }
[ForeignKey("Blog")]
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Then I have the following view-model in which I like to tell AutoMapper to map the Blog object into the BlogViewModel notice the CategoryName property will need to come from Blog.Category.Name and each Comment in the Blog.Comments need to be converter to CommentViewModel using the organic convention.
I currently set the mapping at run time using reflection for any class that implements the ICustomMap interface. Please read the comment in the code over the Transfer(IMapper mapper) method.
public class BlogViewModel : ICustomMapFrom
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string MyCatName { get; set; }
public IEnumerable<CommentViewModel> Comments { get; set; }
// **IMPORTANT NOTE**
// This method is called using reflection when the on Application_Start() method.
// If IMapper is the wrong Interface to pass, I can change
// the implementation of ICustomMap
// I assumed that `IMapper` is what is needed to add configuration at runtime.
public void Transfer(IConfigurationProvider config)
{
// How to I do the custom mapping for my MyCatName and Comments?
// I need to use the config to create the complex mapping
// AutoMapper.Mapper.Map(typeof(Blog), typeof(BlogViewModel));
}
}
Finally here is my CommentViewModel
public class CommentViewModel : IMapFrom<Comment>
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
How can I tell AutoMapper how to map the CategoryName and the Comments?
Updated
Here is how I would create the mapping. I would have the following 3 interfaces
public interface IMap
{
}
public interface IMapFrom<T> : IMap
{
}
public interface ICustomMapFrom : IMap
{
void Map(IConfigurationProvider config);
}
Then in the Global.cs file
I would execute the Run method on startup. Basically this method will scan assemblies and register the classes that I would want to register using the interfaces.
public class ConfigureAutoMapper
{
public void Run()
{
var types = AssemblyHelpers.GetInternalAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && typeof(IMap).IsAssignableFrom(x))
.ToList();
RegisterStandardMappings(types);
RegisterCustomMappings(types);
}
private static void RegisterStandardMappings(IEnumerable<Type> types)
{
foreach (Type type in types)
{
if(type.IsGenericType && typeof(IMapFrom<>).IsAssignableFrom(type))
{
AutoMapper.Mapper.Map(type.GetGenericArguments()[0], type);
}
}
}
private static void RegisterCustomMappings(IEnumerable<Type> types)
{
foreach (Type type in types)
{
if (typeof(ICustomMapFrom).IsAssignableFrom(type))
{
ICustomMapFrom map = (ICustomMapFrom)Activator.CreateInstance(type);
var t = AutoMapper.Mapper.Configuration;
map.Map(Mapper.Configuration);
}
}
}
}
I wrote an NUnit test which sets up AutoMapper with your classes. AutoMapper supports the mapping of CategoryName out of the box.
[TestFixture]
public class TestClass
{
[Test]
public void Test1()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Blog, BlogViewModel>();
});
config.AssertConfigurationIsValid();
var blog = new Blog()
{
Body = "Blog body",
Category = new Category { Name = "My Category" },
Comments = new List<Comment>() {
new Comment { Body = "Comment body 1" },
new Comment { Body = "Comment body 2" }
}
};
var mapper = config.CreateMapper();
var result = mapper.Map<Blog, BlogViewModel>(blog);
Assert.AreEqual(blog.Body, "Blog body");
Assert.AreEqual(blog.Category.Name, result.CategoryName);
List<CommentViewModel> comments = result.Comments.ToList();
Assert.That(comments.Any(c => c.Body == "Comment body 1"));
Assert.That(comments.Any(c => c.Body == "Comment body 2"));
}
}
I have the following DB (Infrastructure) classes:
[Table("ApplicationDriverEquipments")]
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public int TypeId { get; set; }
public virtual ApplicationDriverEquipmentType Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
[Table("ApplicationDriverEquipmentTypes")]
public partial class ApplicationDriverEquipmentType
{
public ApplicationDriverEquipmentType()
{
Equipments = new HashSet<ApplicationDriverEquipment>();
}
public int Id { get; set; }
[Required]
[StringLength(256)]
public string Name { get; set; }
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
and the following DTO (Domain) classes:
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public static class ApplicationDriverEquipmentTypeStaticStringsDomain
{
public const string Tractor = "Tractor";
public const string Trailer = "Trailer";
public const string StraightTruck = "Straight Truck";
public const string CargoVan = "Cargo Van";
}
I wrote the following Automapper rules to resolve it:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan));
I got an error:
Expression 'c => c.Type.Name' must resolve to top-level member and not
any child object's properties. Use a custom resolver on the child type
or the AfterMap option instead.
UPDATE
I rewrote maps:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest)=> dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan);
but now I got an error:
Type Map configuration: ApplicationDriverEquipmentTractorDomain ->
ApplicationDriverEquipment
Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain ->
Infrastructure.Asset.ApplicationDriverEquipment
Property: Type ---> AutoMapper.AutoMapperMappingException: Missing
type map configuration or unsupported mapping.
Mapping types:
String -> ApplicationDriverEquipmentType
System.String -> Infrastructure.Asset.ApplicationDriverEquipmentType
Seems, I don't understand how to map it correctly
You are trying to map from
ApplicationDriverEquipmentTractorDomain.Type is a string
to
ApplicationDriverEquipment.Type is a ApplicationDriverEquipmentType
Where is your mapping configuration for that?
Is it even possible to map a string to a ApplicationDriverEquipmentType?
Sure, you can have a string Name, but where do you get the Id and Equipments?
I suspect you don't want to create a new instance of that type each time you map, but rather you need to look up an instance from some dictionary, sort of a registry pattern
To implement this idea, you simply need to
Load all of the ApplicationDriverEquipmentType from DB
Put them in a dictionary (assuming name is unique)
Register a custom type converter or custom value resolver as below
One way to implement this would be to use a custom type converter
You could use something like
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
And put in your own function that would resolve your ApplicationDriverEquipmentType by name, assuming name is unique like this:
var applicationEquipments = new ApplicationDriverEquipmentTypeRepository().FindAll(); // get all the values somehow from db
var dictionary = applicationEquipments.ToDictionary(x=>x.Name);
Func<string, ApplicationDriverEquipmentType> resolver = x=>dictionary[x];
Yet another way to do this would be to use a custom value resolver
Essentially, the idea would be the same - map of pre-loaded objects, only the way you "plug it in" would be different
Try to use MapFrom method instead:
.ForMember(c => c.Type.Name, p => p.MapFrom(s => Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));
I know that Automapper can automatically map from:
class SomeClassA
{
public int Id { get; set; }
public B Member { get; set; }
}
class SomeClassB
{
public int Id { get; set; }
public string Name { get; set; }
}
to:
class SomeClassADto
{
public int Id { get; set; }
public int Member_Id { get; set; }
public string Member_Name { get; set; }
}
But how can I make Automapper map from my SomeClassADto to SomeClassA automatically?
No the prettiest thing in the world, but it does work. Downside is maintenance is a pain as you add properties to the DTO and class you need to update the mapping.
var config = new MapperConfiguration(x =>
{
x.CreateMap<SomeClassADto, SomeClassB>()
.ForMember(i => i.Id, i => i.MapFrom(src => src.Member_Id))
.ForMember(i => i.Name, i => i.MapFrom(src => src.Member_Name));
x.CreateMap<SomeClassADto, SomeClassA>()
.AfterMap((s, d, r) => d.Member = r.Mapper.Map<SomeClassB>(s));
});
IMapper mapper = config.CreateMapper();
var foo = mapper.Map<SomeClassA>(new SomeClassADto() { Id = 1, Member_Id = 2, Member_Name = "Name" });
I am having two classes :
source.Employee and destination.Employee .
I am getting just two name in my function destination property name i.e destination.TestEnum1 and source property name i.e source.TestEnum1.
I want to create expression dynamically as i mentioned below.
var mapExpr = Mapper.CreateMap<Soure.Employee, destination.Employee>().ForMember(destination => destination.TestEnum1, opt => opt.MapFrom(source => (destination.MyEnum2)source.TestEnum1));
The expression is just
destination => destination.TestEnum1, opt => opt.MapFrom(source => (destination.MyEnum2)source.TestEnum1)
I am creating it to map Enum in Project().To();.
As
Mapper.CreateMap<Soure.MyEnum1, destination.MyEnum2>()
gives exception unable to map MyEnum2 to int 32.
Source employee :
namespace Soure
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Department dept1 { get; set; }
public int age { get; set; }
public MyEnum1 TestEnum1 { get; set; }
}
public enum MyEnum1
{
red = 1,
yellow = 2
}
}
destination employee class :
namespace destination
{
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int age { get; set; }
public MyEnum2 TestEnum1 { get; set; }
public Departments dept1 { get; set; }
}
public enum MyEnum2
{
red = 1,
yellow = 2
}
}
You might think of using Custom Type Converters for this, found here.
Here I made a sample application for you (simplified a bit) where I implemented a Enum1to2TypeConverter inheriting from the generic interface ITypeConverter:
public class Employee1
{
public int Id { get; set; }
public string Name { get; set; }
public MyEnum1 TestEnum { get; set; }
}
public enum MyEnum1
{
red = 1,
yellow = 2
}
public class Employee2
{
public int Id { get; set; }
public string Name { get; set; }
public MyEnum2 TestEnum { get; set; }
}
public enum MyEnum2
{
red = 1,
yellow = 2
}
public class Enum1to2TypeConverter : ITypeConverter<MyEnum1, MyEnum2>
{
public MyEnum2 Convert(ResolutionContext context)
{
return (MyEnum2)(context.SourceValue);
}
}
public class Test
{
public void Example()
{
Mapper.CreateMap<MyEnum1, MyEnum2>().ConvertUsing(new Enum1to2TypeConverter());
Mapper.CreateMap<Employee1, Employee2>();
Mapper.AssertConfigurationIsValid();
var source = new Employee1
{
Id = 1,
Name = "Employee1-Name",
TestEnum = MyEnum1.yellow
};
Employee2 result = Mapper.Map<Employee1, Employee2>(source);
//Check content of result here!
}
}
class Program
{
private static void Main(string[] args)
{
(new Test()).Example();
}
}
As the enums in your example share the same underlying int types, automapper will handle this automatically as follows:
Mapper.CreateMap<Foo, Bar>()
.ForMember(dest => dest.MyEnum2, opt => opt.MapFrom(src => src.MyEnum1))
;
var source = new Foo { MyEnum1 = MyEnum1.yellow };
var destination = Mapper.Map<Bar>(source);
Console.WriteLine(destination.MyEnum2);
Working Fiddle
You can check out ConvertProjectionUsing, which is the ConvertUsing for LINQ.
Mapper.CreateMap<MyEnum1, MyEnum2>().ProjectUsing(src => ???);
What should you put inside that ???, I'm not sure, it will depend on your query provider. EF may or may not be able to take that projection and convert it to SQL. You might be able to cast the source to int:
Mapper.CreateMap<MyEnum1, MyEnum2>().ProjectUsing(src => (MyEnum2)(int)src);
That is completely dependent on your query provider, though.