Is there a way to do the mapping using Automapper - c#

The TeacherSubjects list in TeacherVM always shows null even though Automapper is used to map SubjectVM to TeacherSubject.
I have tried the code below with the automapper configuration. SchoolName is pulling through but TeacherSubjectlist is always null.
public class Teacher
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public School WorkingSchool { get; set; }
public int SchoolId { get; set; }
public List<TeacherSubject> TeacherSubjectslist { get; set; }
}
public class TeacherSubject
{
public int TeacherSubjectId { get; set; }
public Subject Subject { get; set; }
public int SubjectId { get; set; }
public Teacher Teacher { get; set; }
public int TeacherId { get; set; }
}
public class TeacherVM
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public List<SubjectVM> TeacherSubjects { get; set; }
}
public class SubjectVM
{
public string SubjectName { get; set; }
public int SubjectId { get; set; }
}
CreateMap<domain.TeacherSubject, SubjectVM>()
.ForMember(dest => dest.SubjectName, opt => opt.MapFrom(src =>
src.Subject.SubjectName))
.ForMember(dest => dest.SubjectId, opt => opt.MapFrom(src =>
src.Subject.SubjectId));
CreateMap<domain.Teacher, TeacherVM>()
.ForMember(dest => dest.SchoolName, opt => opt.MapFrom(src =>
src.WorkingSchool.SchoolName))
.ForMember(dest => dest.TeacherSubjects, opt => opt.MapFrom(src
=> src.TeacherSubjectslist));
TeacherSubjectlist should be a list of the SubjectId and the SubjectName properties.

The problem is that you are missing creating instance of list types inside each class that contains list of objects.
What you need to do is add instance of list type in class constructor.
With out testing it, for example:
public Teacher()
{
TeacherSubjectslist = new List<TeacherSubject>();
..
..
The same concept is valid for the remaining classes that have list of objects.

Related

AutoMapperMappingException Strange Behaviour

I've found some strange behaviour regarding AutoMapper when I'm trying to map from this class
public class SkinAnalyzerResult {
public Guid Id { get; set; }
public ICollection<SkinAnalyzerQuestionAnswer> SelectedAnswers { get; set; }
public string Description { get; set; }
public ICollection<Product> RecommendedProducts { get; set; }
}
public class SkinAnalyzerQuestionAnswer {
public Guid Id { get; set; }
public String Answer { get; set; }
public ICollection<SkinAnalyzerResult> Results { get; set; }
public SkinAnalyzerQuestion Question { get; set; }
}
to this class
public class SkinAnalyzerResultDataModel {
[Key] public Guid Id { get; set; }
[Required] public ICollection<SkinAnalyzerResultQuestionAnswerDataModel> SelectedAnswers { get; set; }
public string Description { get; set; }
public ICollection<SkinAnalyzerResultProductDataModel> RecommendedProducts { get; set; }
public SkinAnalyzerDataModel SkinAnalyzer { get; set; }
}
public class SkinAnalyzerResultQuestionAnswerDataModel {
public Guid ResultId { get; set; }
public SkinAnalyzerResultDataModel Result { get; set; }
public Guid QuestionAnswerId { get; set; }
public SkinAnalyzerQuestionAnswerDataModel QuestionAnswer { get; set; }
}
public class SkinAnalyzerQuestionAnswerDataModel {
[Key] public Guid Id { get; set; }
[Required] public String Answer { get; set; }
public SkinAnalyzerQuestionDataModel Question { get; set; }
public ICollection<SkinAnalyzerResultQuestionAnswerDataModel> Results { get; set; }
}
using this config map
cfg.CreateMap<SkinAnalyzerResultDataModel, SkinAnalyzerResult>()
.ForMember(dest => dest.SelectedAnswers,
opt => opt.MapFrom(
src => src.SelectedAnswers.Select(x => x.QuestionAnswer)
))
.ForMember(dest => dest.RecommendedProducts,
opt => opt.MapFrom(
src => src.RecommendedProducts.Select(x => x.Product)
))
.ReverseMap();
When I tried to do that I got this AutoMapperMappingException
as you can see I've specified to map SkinAnalyzerResult.Answers into SkinAnalyzerResultDataModel.SelectedAnswers.QuestionAnswer
is there something that I missed or do something wrong?
cfg.CreateMap<SkinAnalyzerResultDataModel, SkinAnalyzerResult>()
.ForMember(dest => dest.SelectedAnswers,
opt => opt.MapFrom(
src => src.SelectedAnswers.Select(x => x.QuestionAnswer)
))
The type of SelectedAnswers is ICollection<SkinAnalyzerQuestionAnswer> while the type of QuestionAnswer is SkinAnalyzerQuestionAnswerDataModel, so you also need to create mapping between the two models:
CreateMap<SkinAnalyzerQuestionAnswerDataModel, SkinAnalyzerQuestionAnswer>()
.ForMember( //config if necessary)

How to flattening child object used repeatedly in C# with Automapper

Using the following entities
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
}
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedByUser { get; set; }
public User DeletedByUser { get; set; }
}
How do I flatten this to the GeneralEntityDto below?
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
I have tried setting up my mappings as seen below but it fails with a complaint about "CreatedByUsername" and "DeletedByUsername" not being mapped.
protected void Configure()
{
CreateMap<GeneralEntity, GeneralEntityDto>()
.ForMember(dest => dest.CreatedByUsername,
opt => opt.MapFrom(src => src.CreatedByUser.Username))
.ForMember(dest => dest.DeletedByUsername, opt =>
opt.MapFrom(src => src.DeletedByUser.Username));
}
You can use the naming convention that automapper provides.
Basically if you include the exact string of the property name of the source Object you do not have to add ForMember() automapper is clever enough to do it automatically.
That means for example :
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedBy { get; set; } // renaming just for simplicity
public User DeletedBy { get; set; } // renaming just for simplicity
}
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
Reference also to these:
http://docs.automapper.org/en/stable/Flattening.html
AutoMapper TwoWay Mapping with same Property Name

AutoMapper Infinite Loop using EF Code First

I have the following classes (One-One relationship Asset-TrackingDevice):
public class Asset
{
public int Id { get; set; }
public string Name { get; set; }
public TrackingDevice TrackingDevice { get; set; }
}
public class TrackingDevice
{
public int Id { get; set; }
public string Imei { get; set; }
public int? AssetId { get; set; }
public Asset Asset { get; set; }
}
The viewModels are very similar:
public class AssetViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int? TrackingDeviceId { get; set; }
public TrackingDeviceViewModel TrackingDevice { get; set; }
}
public class TrackingDeviceViewModel
{
public int Id { get; set; }
public string Imei { get; set; }
public AssetViewModel Asset { get; set; }
public string AssetId { get; set; }
}
Mappings:
CreateMap<Asset, AssetViewModel>()
.ForMember(d => d.TrackingDevice, map => map.Ignore());
CreateMap<AssetViewModel, Asset>()
.ForMember(d => d.TrackingDevice, map => map.Ignore());
CreateMap<AssetViewModel, Asset>()
.ReverseMap();
CreateMap<TrackingDevice, TrackingDeviceViewModel>()
.ForMember(d => d.Asset, map => map.Ignore());
CreateMap<TrackingDeviceViewModel, TrackingDevice>()
.ForMember(d => d.Asset, map => map.Ignore());
CreateMap<TrackingDevice, TrackingDeviceViewModel>()
.ReverseMap();
When I perform a database query to obtain the TrackingDevices,
I get an error because in the mapping the Asset within Tracking Device also includes a Tracking Device and so on.
The query that I execute to obtain the tracking devices is:
var trackingDevices = _appContext.TrackingDevices
.Include(td => td.Asset)
.ToListAsync();
var trackingMapper = Mapper.Map<IEnumerable<TrackingDeviceViewModel>>(trackingDevices);
I read that by including the Map.Ignore would fix the problem but it did not work either, does anyone know what my error is?

Mapping to nested value Automapper

I'm struggling to map 2 objects. Basically have Product which is my EF model, and I'm mapping this to ProductDto, which has FileDto.
I'd like to map Product.FileName to ProductDto.File.Internal name, how to do this?
Classes below.
public class Product : BaseEntity<long>
{
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string Barcode { get; set; }
public int ShelfLife { get; set; }
public int Weight { get; set; }
public bool HasAllergens { get; set; }
[MaxLength(100)]
public string FileName { get; set; }
[ForeignKey("Id")]
public int CustomerId { get; set; }
public virtual ICollection<ProductIngredient> ProductIngredient { get; set; }
public virtual ICollection<Nutrition> Nutritions { get; set; }
public virtual ICollection<ProductComposition> Composition { get; set; }
public virtual IList<ProductionProcess> ProductionProcess { get; set; }
}
public class ProductDto
{
public long Id { get; set; }
public DateTime CretedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
public int ShelfLife { get; set; }
public int Weight { get; set; }
public bool HasAllergens { get; set; }
public int CustomerId { get; set; }
public FileDto File { get; set; }
public IList<IngredientDto> Ingredients { get; set; }
public IList<NutritionDto> Nutritions { get; set; }
public IList<ProductCompositionDto> Composition { get; set; }
public IList<ProductionProcessDto> ProductionProcess { get; set; }
}
public class ProductionProcessDto
{
public string Key { get; set; }
public string Value { get; set; }
public FileDto File { get; set; }
}
public class NutritionDto
{
public string Key { get; set; }
public string Value { get; set; }
}
public class ProductCompositionDto
{
public string Key { get; set; }
public string Value { get; set; }
}
File Dto:
public class FileDto
{
public string Base64EncodedFile { get; set; }
public string OriginalName { get; set; }
public string InternalName { get; set; }
public string Type { get; set; }
}
Automapper so far:
//Product
CreateMap<Nutrition, NutritionDto>().ReverseMap();
CreateMap<ProductComposition, ProductCompositionDto>().ReverseMap();
CreateMap<ProductionProcessDto, ProductionProcess>()
.ForMember(dest => dest.FileInternalName, opt => opt.MapFrom(src => src.File.InternalName))
.ForMember(dest => dest.FileOriginalName, opt => opt.MapFrom(src => src.File.OriginalName))
.ReverseMap();
CreateMap<Product, ProductDto>()
.ForMember(d => d.File, o => o.MapFrom(s => Mapper.Map<Product, FileDto>(s)))
.ForMember(d => d.Nutritions, o => o.MapFrom(s => s.Nutritions))
.ForMember(d => d.Composition, o => o.MapFrom(s => s.Composition))
.ForMember(d => d.ProductionProcess, o => o.MapFrom(s => s.ProductionProcess))
.ForMember(d => d.Ingredients, o => o.MapFrom(s => s.ProductIngredient.Select(pi => pi.Ingredients)))
.ReverseMap();
CreateMap<ProductDto, Product>()
.ForMember(d => d.FileName, o => o.MapFrom(s => s.File.InternalName))
.ReverseMap();
I am able to map from ProductDto (on data post) to Product but not other way around, all help much appreciated
Thanks
This code solved my issue:
.ForMember(d => d.File, o => o.MapFrom(model => new FileDto { InternalName = model.FileName }))
Applied to:
CreateMap<Product, ProductDto>()

Automapper - Unable to map nested objects/collecions

I've tried numerous examples on here and from the automapper wiki and I am still unable to get this issue resolved. I am trying to map a nested object and a nested collection and no matter what I do it always throws an error. The only way I can get the controller to return data is by turning on option.ignore for the two properties.
These are the business layer objects I am trying to map
public class LocationBL
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public string Country { get; set; }
public DbGeography Coordinates { get; set; }
public int LocationType_Id { get; set; }
public virtual LocationTypeBL LocationType { get; set; }
public virtual ICollection<SportBL> Sports { get; set; }
}
public class LocationTypeBL
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<LocationBL> Locations { get; set; }
}
public class SportBL
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<LocationBL> Locations { get; set; }
public virtual ICollection<UserBL> Users { get; set; }
}
These are the data layer objects
public class Location : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Company")]
public int? CompanyId { get; set; }
[Required]
public string Name { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public string Country { get; set; }
[Required]
public DbGeography Coordinates { get; set; }
[ForeignKey("LocationType")]
public int LocationType_Id { get; set; }
public virtual LocationType LocationType { get; set; }
public virtual ICollection<Sport> Sports { get; set; }
public virtual Company Company { get; set; }
}
public class LocationType : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
public class Sport : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Location> Locations { get; set; }
public virtual ICollection<User> Users { get; set; }
}
This is my mapping profile
public class LocationProfile : Profile
{
public LocationProfile()
{
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
CreateMap<Location, LocationBL>()
.ForMember(Dest => Dest.Sports,
opt => opt.MapFrom(src => src.Sports))
.ForMember(Dest => Dest.LocationType,
opt => opt.MapFrom(src => src.LocationType));
CreateMap<LocationBL, Location>()
.ForMember(Dest => Dest.Sports,
opt => opt.MapFrom(src => src.Sports))
.ForMember(Dest => Dest.LocationType,
opt => opt.MapFrom(src => src.LocationType));
}
}
UPDATE *******
This is my LocationType profile
public class LocationTypeProfile : Profile
{
public LocationTypeProfile()
{
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
}
}
This is my Sport profile
public class SportProfile : Profile
{
public SportProfile()
{
CreateMap<Sport, SportBL>();
CreateMap<SportBL, Sport>();
}
}
Not sure if it matters but this is an Azure Mobile App backend using Autofac, WebAPI, and OWIN. This is my first time using AutoMapper and Autofac so please forgive me as I am still learning. The profiles are all registered and if I set the nested objects to ignore, the controller returns the proper data.
Thank you in advance!!!
You are almost there. You need to instruct AutoMapper on how to map the nested objects as well. So you need to create a map for the Sport to SportBL, and vice-versa, also.
// use ForMember if needed, but you know how to do that so I won't
// show it.
CreateMap<Sport, SportBL>();
Then AutoMapper will use that mapping when it mapping nested complex types.
Another note, if your classes have the same properties, you can just call the ReverseMap() method and it will do bidirectional mapping for you.
So instead of this:
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
You can just do this to accomplish the same thing:
Mapper.CreateMap<LocationType, LocationTypeBL>().ReverseMap();

Categories