I'm starting to use AutoMapper for my project.
For this I want to do the following 'one-to-many' mapping:
public class Team
{
string TeamName { get; set; }
List<Person> Member { get; set; }
}
public class Person
{
string PersonName { get; set; }
}
Destination:
public class TeamDetailsViewModel
{
string TeamName { get; set; }
string PersonName { get; set; }
}
How to proceed with AutoMapper? Is this possible?
Thanks a lot in advance.
One approach is to use two map methods sequentially like this:
team = AutoMapper.Mapper.Map<Team, TeamDetailsViewModel>(Team);
/* Pass the created destination to the second map call: */
AutoMapper.Mapper.Map<Person, TeamDetailsViewModel>(Person, team);
Another approach is to apply a very simple extension method:
public static TDestination Map<TSource, TDestination>(
this TDestination destination, TSource source)
{
return Mapper.Map(source, destination);
}
and the usage is:
var teamDetailsVM = Mapper.Map<TeamDetailsViewModel>(Team)
.Map(Person);
Use ForMember method
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<Team, TeamDetailsViewModel>().ForMember(des => des.TeamName,
op => op.MapFrom(team => team.Member.FirstOrDefault().PersonName)));
var mapper = config.CreateMapper();
var team = new Team();
TeamDetailsViewModel dto = mapper.Map<TeamDetailsViewModel>(team);
this method allow you to customize your mapping
Related
I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}
Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly
I am having an issue when mapping nested classes that reference each other. Entity framework populares the nested entities in a loop so I end up with the parent nested inside the nested child entity after mapping.
I made a sample program you can copy paste that shows the problem. In my actual program I am mapping collections so it would require to loop over entire collection to set all the nested object to null and that does not feel neat, I would rather adjust my mapping rules if possible.
Here is code that shows the issue:
using AutoMapper;
public class Job
{
public string Name { get; set; }
public PayPackage PayPackage { get; set; }
}
public class PayPackage
{
public string Name { get; set; }
public Job Job { get; set; }
}
public class JobViewModel
{
public string Name { get; set; }
public PayPackageViewModel PayPackage { get; set; }
}
public class PayPackageViewModel
{
public string Name { get; set; }
public JobViewModel Job { get; set; }
}
class Program
{
static void Main(string[] args)
{
var job = new Job
{
Name = "Job Name",
PayPackage = new PayPackage
{
Name = "Pay Package Name"
}
};
job.PayPackage.Job = job; //simulate how EF is populating entity
var config = new MapperConfiguration(c =>
{
c.CreateMap<Job, JobViewModel>();
c.CreateMap<JobViewModel, Job>();
c.CreateMap<PayPackage, PayPackageViewModel>();
c.CreateMap<PayPackageViewModel, PayPackage>();
});
var mapper = config.CreateMapper();
var jobVm = mapper.Map<JobViewModel>(job);
Assert.IsTrue(jobVm.PayPackage != null);
Assert.IsTrue(jobVm.PayPackage.Job == null); //how do I specify mapping so this passes?
}
}
What is the best way to avoid the parent appearing twice in the mapped result?
E.g.
c.CreateMap<Job, JobViewModel>()
.ForMember(dest => dest.PayPackage, opt => opt.Ignore());
The PayPackage property of the destination JobViewModel object will be ignored when the mapping occurs.
I'm trying to use AutoMapper v6.1.1 to map a class using projection, but AutoMapper doesn't include deeply nested objects.
I've attached a complete Visual Studio 2015 solution with a unit test here:
https://www.dropbox.com/s/omue5ou5dvxsa57/UnitTestProject2.zip?dl=0
I'm basically trying to map a Child and Parent hierarchy into a Person hierarchy, but the grand-Parents aren't getting included in the projection result.
Models:
public class Child
{
public string Name { get; set; }
public virtual Parent Parent { get; set; }
}
public class Parent
{
public string Name { get; set; }
public virtual Parent GrandParent { get; set; }
}
public class Person
{
public string Name { get; set; }
public virtual Person Parent { get; set; }
}
Mapping profile:
public class PersonProfile : Profile
{
public PersonProfile()
{
this.CreateMap<Child, Person>()
.MaxDepth(5);
this.CreateMap<Parent, Person>()
.ForMember(destinationMember => destinationMember.Parent, memberOptions => memberOptions.MapFrom(sourceMember => sourceMember.GrandParent))
.MaxDepth(5);
}
}
Unit test:
[TestClass]
public class UnitTest1
{
IMapper mapper;
List<Child> children;
[TestInitialize]
public void TestInitialize()
{
MapperConfiguration configuration = new MapperConfiguration((config =>
{
config.AddProfile(new PersonProfile());
config.ForAllMaps((mapType, mapperExpression) =>
{
mapperExpression.MaxDepth(5);
});
}));
this.mapper = configuration.CreateMapper();
mapper.ConfigurationProvider.AssertConfigurationIsValid();
this.children = new List<Child>
{
new Child
{
Name = "Child1",
Parent = new Parent
{
Name = "Parent1",
GrandParent = new Parent
{
Name = "GrandParent1",
GrandParent = new Parent
{
Name = "GreatGrandParent1"
}
}
}
}
};
}
[TestMethod]
public void TestProjection()
{
IQueryable<Person> people = children.AsQueryable().ProjectTo<Person>(mapper.ConfigurationProvider);
AssertPeople(people);
}
[TestMethod]
public void TestMap()
{
List<Person> people = mapper.Map<List<Child>, List<Person>>(children);
AssertPeople(people.AsQueryable());
}
private void AssertPeople(IQueryable<Person> people)
{
Assert.IsNotNull(people);
Assert.AreEqual(1, people.Count());
Person child1 = people.ElementAt(0);
Assert.AreEqual("Child1", child1.Name);
Person parent1 = child1.Parent;
Assert.IsNotNull(parent1);
Assert.AreEqual("Parent1", parent1.Name);
Person grandParent1 = parent1.Parent;
Assert.IsNotNull(grandParent1); // fails when using ProjectTo
Assert.AreEqual("GrandParent1", grandParent1.Name);
}
}
Using the Map method works but ProjectTo doesn't.
The classes in the sample code are much simpler than those used in production.
I'm trying to use projection so that I can return an IQueryable<Person> from OData and take advantage of the SQL generated by LINQ to Entities with query options automatically applied.
Any help is appreciated.
Thank you!
I think this describes the issue:
https://github.com/AutoMapper/AutoMapper/issues/2171
But as a workaround is it not possible to create an extension method that basically calls the Map internally:
public static class Extenstions
{
public static IQueryable<TDestination> ProjectToExt<TDestination, TSource>(this IQueryable<TSource> #this,
IMapper mapper)
{
return mapper.Map<IEnumerable<TDestination>>(#this).AsQueryable();
}
}
Then the calling code is like:
IQueryable<Person> people = children.AsQueryable().ProjectToExt<Person, Child>(mapper);
I've just started with AutoMapper in C#. I've succesfully created a mapping like this:
Mapper.CreateMap<InputTypeA, OutputTypeA>()
I've also found a way to add some logic to specific properties, like formatting a date (in InputTypeA) to a string in a specific format (in OutputTypeA).
.ForMember(
dest => dest.MyDateProperty,
opt => opt.ResolveUsing(
src => String.Format("{0:yyyy-MM-dd}", src.MyDateProperty)));
Now I need to do the same for a number of float properties, but I'm wondering if there is a short/easy way to do this, except copying a piece of code like the one above for every property that needs to follow this rule.
I've found that I can create a new map like this for mapping floats to strings:
Mapper.CreateMap<float,string>()
.ConvertUsing(src =>
String.Format(CultureInfo.InvariantCulture.NumberFormat, "{0:0.00}", src));
This works, but is too generic, because I also have a mapping for another type (let's call it InputTypeB), that also contains float properties, which need to be treated differently.
Mapper.CreateMap<InputTypeB, OutputTypeB>()
How can I make the float-to-string mapping part of the first mapping only?
You could create two separate mappers based on two separate configurations, only one of which includes the float-to-string mapping:
public class InputTypeA { public float Foo { get; set; } }
public class OutputTypeA { public string Foo { get; set; } }
public class InputTypeB { public float Bar { get; set; } }
public class OutputTypeB { public string Bar { get; set; } }
public class Program
{
public static void Main(string[] args)
{
Func<float, string> mapFunc =
src => String.Format(CultureInfo.InvariantCulture.NumberFormat, "{0:0.00}", src);
var floatToStringConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<InputTypeA, OutputTypeA>();
cfg.CreateMap<float, string>().ConvertUsing(mapFunc);
});
var regularConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<InputTypeB, OutputTypeB>();
});
IMapper floatToStringMapper = floatToStringConfig.CreateMapper();
IMapper regularMapper = regularConfig.CreateMapper();
var aIn = new InputTypeA() { Foo = 1f };
var aOut = floatToStringMapper.Map<OutputTypeA>(aIn);
Console.WriteLine(aOut.Foo); // prints "1.00"
var bIn = new InputTypeB() { Bar = 1f };
var bOut = regularMapper.Map<OutputTypeB>(bIn);
Console.WriteLine(bOut.Bar); // prints "1"
}
}
You can create custom value resolvers for each case you need to handle. Then apply these to the appropriate members in your mappings.
As an example, I need to map from TypeA to TypeB, I only want DateB to use the custom conversion:
public class TypeA {
public DateTime DateA { get; set; }
public DateTime DateB { get; set; }
}
public class TypeB {
public string DateA { get; set; }
public string DateB { get; set; }
}
I create a custom resolver:
class DateStringResolver : ValueResolver<DateTime, string> {
protected override string ResolveCore(DateTime source) {
return String.Format("{0:yyyy-MM-dd}", source);
}
}
Then in my mapper config:
Mapper.CreateMap<TypeA, TypeB>()
//Only Date B will use our custom resolver
.ForMember(d => d.DateB, opt => opt.ResolveUsing<DateStringResolver>().FromMember(src => src.DateA));
The resolver can now be applied wherever it is needed.
Docs:https://github.com/AutoMapper/AutoMapper/wiki/Custom-value-resolvers
I've been looking over how to use Inheritance in AutoMapper but I'm struggling to get it working fully with Linq. Here is my code:
I have defined my mappings here:
CreateMap<Article, ArticleDetailsViewModel>()
.Include<Article, ArticleNewsItemDetailsViewModel();
CreateMap<Article, ArticleNewsItemDetailsViewModel>();
ArticleDetailsViewModel is a base class of ArticleNewsItemDetailsViewModel.
Now here lies the problem, if I had:
CreateMap<ArticleNewsItem, ArticleNewsItemDetailsViewModel>();
All of the properties in the view model would automatically map because they are the same name as their Linq object counterpart. However, because I am using the Article => ArticleNewsItemDetailsViewModel mapping this is not possible, instead I would have to define each one as:
.ForMember(x => x.Property1, opt => opt.MapFrom(src => src.ArticleNewsItem.Property1)
I thought about moving all properties from ArticleNewsItemDetailsViewModel into a new view model and having that class a property within the ArticleNewsItemDetailsViewModel and as long as there is a mapping between those two objects then it will work, but it doesn't feel very clean.
Is there any way to avoid having to do this?
Supposing you have the following classes:
public class Article
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public ArticleNewsItem ArticleNewsItem { get; set; }
}
public class ArticleDetailsViewModel
{
public string Prop1 { get; set; }
}
public class ArticleNewsItemDetailsViewModel : ArticleDetailsViewModel
{
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class ArticleNewsItem
{
public string Prop3 { get; set; }
}
The mapping should look like below:
var res = Mapper.Map<Article, ArticleNewsItemDetailsViewModel>(_article);
Mapper.Map(_article.ArticleNewsItem, res);
Moreover you can create custom type converter to avoid writing these two lines every time you need to map Article to ArticleNewsItemDetailsViewModel.
Apologies if I am over simplifying this in my head but can't you simply add the direct mapping you mention:
CreateMap<ArticleNewsItem, ArticleNewsItemDetailsViewModel>();
To me this is the simplest and cleanest solution...
EDIT
Sorry, I misunderstood. You can't map an object to a nested property without creating a custom map via .ConstructUsing() or .ConvertUsing() methods (or doing it the untidy way)...
Mapper.CreateMap<Article, ArticleNewsItemDetailsViewModel>().ConstructUsing(ConstructItem)
..Then create your method to build the ArticleNewsItemDetailsViewModel...
private static ArticleNewsItemDetailsViewModel ConstructItem(Article source)
{
var newsItem = new ArticleNewsItem
{
Prop1 = source.Prop1,
Prop2 = source.Prop2
};
var result = new ArticleNewsItemDetailsViewModel()
{
ArticleNewsItem = newsItem
};
return result;
}
However I would still recommend re implementing your solution so you are mapping 'like for like'. Here is a good example: http://automapper.codeplex.com/wikipage?title=Nested%20Mappings
Assuming all the required properties are in Article you could create a Custom Value Resolver to do this e.g.
public class ArticleNewsItemResolver : ValueResolver<Article, ArticleNewsItem>
{
protected override ArticleNewsItem ResolveCore(Article source)
{
return Mapper.DynamicMap<Article, ArticleNewsItem>(source);
}
}
...
CreateMap<Article, ArticleNewsItemDetailsViewModel>()
.ForMember(src => src.NewsItem, opt => opt.ResolveUsing<ArticleNewsItemResolver>());