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);
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'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
I'm trying to Map and ReverseMap an ImmutableHashSet property to an ICollection using AutoMapper.
The Automapper successfully maps the ImmutableHashSet property to ICollection but it fails to map the ICollection back to ImmutableHashSet
Here is the minimal reproducible example:
Consider I have Order and OrderItem class as below:
public class Order
{
private HashSet<OrderItem> _orderItems;
public Order()
{
_orderItems = new HashSet<OrderItem>();
}
public ImmutableHashSet<OrderItem> OrderItems
{
get => _orderItems.ToImmutableHashSet();
private set => _orderItems = value.ToHashSet();
}
public void AddOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
public void RemoveOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
}
public class OrderItem
{
public OrderItem(int orderId, string orderName)
{
OrderId = orderId;
OrderName = orderName;
}
public int OrderId { get; private set; }
public string OrderName { get; private set; }
}
The Order and OrderItem classes need to be mapped to below OrderDto and classes
public class OrderDto
{
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItemDto
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
The OrderProfile class below defines the Automapper Profile to map Order to OrderDto and visa-versa.
public class OrderProfile : Profile
{
public OrderProfile()
{
CreateMap<Order, OrderDto>()
.ReverseMap();
CreateMap<OrderItem, OrderItemDto>()
.ReverseMap();
}
}
public class OrderItemDto
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
Code to map and reverse map the Order and OrderDto classes:
private static void Map()
{
var mapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new OrderProfile());
}).CreateMapper();
var order = new Order();
order.AddOrderItem(new OrderItem(1, "Laptop"));
order.AddOrderItem(new OrderItem(2, "Keyboard"));
// This code maps correctly
var orderDto = mapper.Map<OrderDto>(order);
// This is where I get an exception
var orderMappedBack = mapper.Map<Order>(orderDto);
}
On mapping the Order object from OrderDto, I get the following exception:
System.ArgumentException: System.Collections.Immutable.ImmutableHashSet`1[ReadOnlyCollectionDemo.OrderItem]
needs to have a constructor with 0 args or only optional args.
(Parameter 'type') at lambda_method(Closure , OrderDto , Order , ResolutionContext )
Any pointers to help fix this issue is highly appreciated.
Update
I somehow got it working through custom Converter. But I really do not know how it worked. I updated my mapper configuration as follows:
var mapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new OrderProfile());
cfg.CreateMap(typeof(ICollection<>), typeof(ImmutableHashSet<>)).ConvertUsing(typeof(HashMapConverter<,>));
}).CreateMapper();
My HashMapConverter class:
public class HashMapConverter<TCollection, TImmutableHashSet>
: ITypeConverter<ICollection<TCollection>, ImmutableHashSet< TImmutableHashSet>>
{
public ImmutableHashSet< TImmutableHashSet> Convert(
ICollection<TCollection> source,
ImmutableHashSet< TImmutableHashSet> destination,
ResolutionContext context)
{
return ImmutableHashSet<TImmutableHashSet>.Empty;
}
}
As you can see I'm just returning an empty ImmutableHashSet, however, to my surprise the OrderDto is now successfully mapped back to Order with the correct number of OrderItems Count. I would have expected the Count to be 0 since I'm returning an empty HashSet. I suspect it is working because OrderItem and OrderItemDto are also mapped through AutoMapper.
I would like to confirm my hypothesis and know if this is a correct approach.
In my similar example I did not get expected values back but an empty collection, so you are better of completing the conversion function with
public class HashMapConverter<TCollection, TImmutableHashSet>
: ITypeConverter<ICollection<TCollection>, ImmutableHashSet<TImmutableHashSet>>
{
public ImmutableHashSet<TImmutableHashSet> Convert(
ICollection<TCollection> source,
ImmutableHashSet<TImmutableHashSet> destination,
ResolutionContext context)
{
var hs =new HashSet<TCollection>(source);
return ((IEnumerable<TImmutableHashSet>)hs).ToImmutableHashSet<TImmutableHashSet>();
//return ImmutableHashSet<TImmutableHashSet>.Empty;
}
}
instead of returning empty;
As it turns out mapping an ImmutableHashSet with the collection was not as straightforward as it seems. However, this made me realize that maybe I was not solving the correct problem. All I wanted was to ensure that my OrderItem HashSet cannot be modified outside the Order class. I could easily achieve this by returning an IReadOnlyCollection.
Here's my updated Order class:
public class Order
{
private HashSet<OrderItem> _orderItems;
public Order()
{
_orderItems = new HashSet<OrderItem>();
}
public IReadOnlyCollection<OrderItem> OrderItems
{
get => _orderItems.ToImmutableHashSet();
private set => _orderItems = value.ToHashSet();
}
public void AddOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
public void RemoveOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
}
IReadOnlyCollection not only helps me to resolve the Automapper issue but in addition to this, it does not expose the Add method. Hence, a user cannot accidentally call order.OrderItems.Add(orderItem) method outside the Order class.
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 using EntityFramework as a DataLayer and DTO to transfer data between layer. I develop Windows Forms in N-Tier architecture and when I try to mapping from Entity to DTO in BLL:
public IEnumerable<CategoryDTO> GetCategoriesPaged(int skip, int take, string name)
{
var categories = unitOfWork.CategoryRepository.GetCategoriesPaged(skip, take, name);
var categoriesDTO = Mapper.Map<IEnumerable<Category>, List<CategoryDTO>>(categories);
return categoriesDTO;
}
I've got this error:
http://s810.photobucket.com/user/sky3913/media/AutoMapperError.png.html
The error said that I missing type map configuration or unsupported mapping. I have registered mapping using profile in this way at UI Layer:
[STAThread]
static void Main()
{
AutoMapperBusinessConfiguration.Configure();
AutoMapperWindowsConfiguration.Configure();
...
Application.Run(new frmMain());
}
and AutoMapper configuration is in BLL:
public class AutoMapperBusinessConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<EntityToDTOProfile>();
cfg.AddProfile<DTOToEntityProfile>();
});
}
}
public class EntityToDTOProfile : Profile
{
public override string ProfileName
{
get { return "EntityToDTOMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<Category, CategoryDTO>();
}
}
public class DTOToEntityProfile : Profile
{
public override string ProfileName
{
get { return "DTOToEntityMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<CategoryDTO, Category>();
}
}
I've got the same error too when mapping from DTO to Entity.
category = Mapper.Map<Category>(categoryDTO);
How to solve this?
Its because you are using Mapper.Initialize multiple times. If you look at the source code it calls Mapper.Reset() which means only the last mapping defined will work. so instead simply remove the Initialize calls and replace with Mapper.AddProfile< >
Use AutoMapper.AssertConfigurationIsValid() after the Configure() calls. If anything fails it will throw an exception with a descriptive text. It should give you more info to debug further.
Mapping DTOs to Entities using AutoMapper and EntityFramework
here we have an Entity class Country and an CountryDTO
public class Country
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
CountryDto
public class CountryDTO
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
Create Object of CountryDTO
CountryDTO collection=new CountryDTO();
collection.CountryID =1;
collection.ContryName ="India";
collection.CountryCode ="in";
Country model = Convertor.Convert<Country, CountryDTO>(collection);
dbcontext.Countries.Add(model);
dbcontext.SaveChanges();
this will work fine for a new Country, the above code will map CountryDTO to Country Entity Object and add new entities to the dbcontext and save the changes.
using System.Reflection;
public static TOut Convert<TOut, TIn>(TIn fromRecord) where TOut : new()
{
var toRecord = new TOut();
PropertyInfo[] fromFields = null;
PropertyInfo[] toFields = null;
fromFields = typeof(TIn).GetProperties();
toFields = typeof(TOut).GetProperties();
foreach (var fromField in fromFields)
{
foreach (var toField in toFields)
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
return toRecord;
}
public static List<TOut> Convert<TOut, TIn>(List<TIn> fromRecordList) where TOut : new()
{
return fromRecordList.Count == 0 ? null : fromRecordList.Select(Convert<TOut, TIn>).ToList();
}
http://bhupendrasinghsaini.blogspot.in/2014/09/convert-enity-framwork-data-in-entity.html