Via AutoMapper, I want to convert CreateUserInputModel to UserModel.
CreateUserInputModel has a property: List<int> Options which accepts IDs of options. UserModel has a property: List<OptionModel> Options which contains the list of OptionModel which has the field Id. I tried to create a mapper ForMember, but when I add it to the mapper, an unusual error appears without exception.
If you have any ideas on how to resolve this mapping, I will be very grateful. Thank you!
CreateUserInputModel
public class CreateUserInputModel
{
public string Email { get; set; } = string.Empty;
public string Firstname { get; set; } = string.Empty;
public string Lastname { get; set; } = string.Empty;
public DateTime EmploymentDate { get; set; }
public int WorkTypeId { get; set; }
public List<int>? Options { get; set; } = new List<int>();
}
UserModel
public class UserModel
{
public int Id { get; set; }
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Firstname { get; set; } = string.Empty;
public string Lastname { get; set; } = string.Empty;
public int VacationDays { get; set; }
public DateTime EmploymentDate { get; set; }
public WorkTypeModel WorkType { get; set; } = new WorkTypeModel();
public List<OptionModel>? Options { get; set; } = new List<OptionModel>();
}
User mapper
CreateMap<UserModel, CreateUserInputModel>()
.ForMember(dest => dest.WorkTypeId, opt => opt.MapFrom(src => src.WorkType.Id))
.ForMember(dest => dest.Options, opt => opt.MapFrom(src => src.Options.Select(option => option.Id).ToList()))
.ReverseMap();
Think that you miss out on the mapping configuration for mapping from int to OptionModel and vice versa.
CreateMap<int, OptionModel>()
.AfterMap((src, dest) =>
{
dest.Id = src;
});
CreateMap<OptionModel, int>()
.ConstructUsing(src => src.Id);
Sample .NET Fiddle
Related
ForMember/MapFrom is somehow not executed whatever I try.
Those are the classes to be mapped;
public class ImageParams : IEntityParams, IImage, IOperatorFields
{
public ImageParams()
{
}
public ImageParams(string userId, string title, string description, string imagePath, bool profilePhoto)
{
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
public ImageParams(int id, string userId, string title, string description, string imagePath, bool profilePhoto)
{
Id = id;
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "userId")]
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[JsonProperty(PropertyName = "imagePath")]
public string ImagePath { get; set; }
[JsonProperty(PropertyName = "profilePhoto")]
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
}
public interface IImage: IEntity, IHasStatus, IDateOperationFields
{
string UserId { get; set; }
string Title { get; set; }
string Description { get; set; }
string ImagePath { get; set; }
bool ProfilePhoto { get; set; }
}
public class Image: IImage
{
public int Id { get; set; }
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public List<ReportImage> Reports { get; set; }
public List<Report> UserReports { get; set; }
}
I create the map as below;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Image, ImageParams>().ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath))).ReverseMap();
}
private static string ImagePathFormatting(string imagePath)
{
var formattedImagePath = imagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? imagePath : $"{AppSettingsProvider.PictureBaseUrl}/{imagePath}";
return formattedImagePath;
}
}
I register my profile as below;
services.AddAutoMapper(typeof(MappingProfile));
And I try to map as below;
public ImageParams GetProfileImage(string userId)
{
var image = Entities.FirstOrDefault(x => x.UserId == userId && x.ProfilePhoto && x.Status == Status.Active);
return _mapper.Map<ImageParams>(image);
}
I am sure the MappingProfile is executed successfully and mapping Image object to ImageParams object as well however ImagePathFormatting function is not called.
I have tried so many variations like instead of ImagePathFormatting I have used anonymous funtion. I have also tried using IValueResolver as below;
public class CustomResolver : IValueResolver<ImageParams, Image, string>
{
public string Resolve(ImageParams source, Image destination, string imagePath, ResolutionContext context)
{
return source.ImagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? source.ImagePath : $"{AppSettingsProvider.PictureBaseUrl}/{source.ImagePath}";
}
}
Whatever I try, I cannot make MapFrom nor CustomResolver invoked.
Any help would be appreciated.
First solution:
Remove from class ImageParams any constructor except default without parameters:
public class ImageParams : IImage
{
public ImageParams()
{
}
//Other members.
}
Second solution:
Add DisableConstructorMapping():
var config = new MapperConfiguration(cfg => {
cfg.AddProfile(new MappingProfile());
cfg.DisableConstructorMapping();
}
);
var mapper = config.CreateMapper();
Third solution:
CreateMap<ImageParams, Image>()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ReverseMap()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ConstructUsing(x => new ImageParams());
Source 1
Source 2
I'd strongly suggest you use AutoMapper Execution Plan Tool tool to see exactly what automapper is doing when it runs the mapping.
Confident that will solve your problem.
For context, I'm in the process of migrating our EF6 Db Context to EF Core 3. Why EF Core 3 only? Currently we're not able to upgrade to the latest EF Core version because of project constraints. We're still using .NET Framework 4.5.6, we're slowly upgrading.
Libaries used
EF Core 3.1.19
Devart.Data.MySql.Entity.EFCore 8.19
The models
public class AutomatedInvestigation
{
public int AutomatedSearchScreenshotId { get; set; }
public int OrderId { get; set; }
public int OrderLineItemId { get; set; }
public int ServiceId { get; set; }
public int? ComponentId { get; set; }
public OrderLineItemResults Result { get; set; }
public int? PageSourceDocumentId { get; set; }
public string Errors { get; set; } = string.Empty;
public DateTime CreateDateTime { get; set; }
public DateTime EditDateTime { get; set; }
public SearchRequestParameters SearchParameters { get; set; }
public Service Service { get; set; }
public Subject Subject { get; set; }
public Order Order { get; set; }
public OrderLineItem OrderLineItem { get; set; }
public virtual Component Component { get; set; }
}
[ComplexType]
public class SearchRequestParameters
{
public SearchRequestParameters()
{
this.Serialized = string.Empty;
}
[NotMapped]
[JsonIgnore]
public string Serialized
{
get { return JsonConvert.SerializeObject(Parameters); }
set
{
if (string.IsNullOrEmpty(value)) return;
var parameters = JsonConvert.DeserializeObject<SearchParameters>(value);
Parameters = parameters ?? new SearchParameters();
}
}
public SearchParameters Parameters { get; set; }
}
[ComplexType]
public class SearchParameters
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime DOB { get; set; }
public string State { get; set; } = string.Empty;
}
The model builder (excluded irrelevant code)
internal static ModelBuilder BuildAutomationInvestigationModel(this ModelBuilder modelBuilder)
{
var entityTypeBuilder = modelBuilder.Entity<AutomatedInvestigation>();
entityTypeBuilder.OwnsOne(s => s.SearchParameters, sa =>
{
sa.OwnsOne(p => p.Parameters, pa =>
{
pa.Property(p => p.FirstName);
pa.Property(p => p.LastName);
pa.Property(p => p.DOB);
pa.Property(p => p.State);
});
});
entityTypeBuilder.ToTable("automated_investigations")
.HasKey(p => p.AutomatedSearchScreenshotId);
entityTypeBuilder.MapProperties()
.MapRelations();
return modelBuilder;
}
private static EntityTypeBuilder<AutomatedInvestigation> MapProperties(this EntityTypeBuilder<AutomatedInvestigation> entityTypeBuilder)
{
entityTypeBuilder.Property(p => p.AutomatedSearchScreenshotId).HasColumnName("automated_investigation_id");
entityTypeBuilder.Property(p => p.OrderId).HasColumnName("order_id").IsRequired();
entityTypeBuilder.Property(p => p.OrderLineItemId).HasColumnName("order_line_item_id").IsRequired();
entityTypeBuilder.Property(p => p.ServiceId).HasColumnName("service_id").IsRequired();
entityTypeBuilder.Property(p => p.ComponentId).HasColumnName("component_id").IsRequired(false);
entityTypeBuilder.Property(p => p.Result).IsRequired();
entityTypeBuilder.Property(p => p.PageSourceDocumentId).IsRequired(false);
entityTypeBuilder.Property(e => e.Errors)
.IsRequired()
.HasColumnName("errors")
.HasColumnType("mediumtext");
entityTypeBuilder.Property(p => p.CreateDateTime).HasColumnName("create_datetime").IsRequired();
entityTypeBuilder.Property(p => p.EditDateTime).HasColumnName("edit_datetime").IsRequired();
return entityTypeBuilder;
}
The error
I've tried adding HasColumnName but throws the same error. I've also tried using [Owned] annotation instead of the OwnsOne on the model builder but throws the same error. Also tried just specifying "SearchParameters" but will throw unknown column on "Parameters".
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.
public class Restaurant
{
public int RestaurantId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public bool Active { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public decimal? Lat { get; set; }
public decimal? Long { get; set; }
}
public class RestaurantInfo
{
public int RestaurantId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public bool Active { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string Address2 { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public decimal? Lat { get; set; }
public decimal? Long { get; set; }
}
Auto Mapper
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Restaurant, RestaurantInfo>();
CreateMap<Restaurant, Address>();
}
}
public RestaurantInfo GetRestaurantById(IMapper mapper)
{
var restaurant = new Restaurant
{
RestaurantId = 1,
Name = "Blueline",
Slug = "Blueline",
Active = true,
Address1 = "XYZ",
Address2 = "PQR"
};
return mapper.Map<Restaurant>(restaurantInfo);
}
My source class is Restaurant and Destination class is RestaurantInfo. Auto mapper is converting Restaurant back into RestaurantInfo, but issue is Address property of RestaurantInfo is not getting initialized with all address related properties of Restaurant. I think my mapping code is not correct. Suggest me correct mapping for above issue.
You can archive that by using ForPath method
// Configure AutoMapper
Mapper.Initialize(
cfg => cfg.CreateMap<Restaurant, RestaurantInfo>()
.ForPath(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address1))
.ForPath(dest => dest.Address.Address2, opt => opt.MapFrom(src => src.Address2))
);
// Perform mapping
var restaurantInfo = Mapper.Map<Restaurant, RestaurantInfo>(restaurant);
Also please referer to the Automapper documentation.
Automapper will flatten an object by convention - https://stackoverflow.com/a/8259466/5121114. Meaning that if you where mapping from RestaurantInfo to Restaurant you could prefix the properties that relate to the Address object with "Address" and automapper would figure out the mapping for you. However what you want to do is unflatten the object and construct an Address object and that is not a feature out of the box. You can possibly write an extension method to achieve this as described in the following post: http://martinburrows.net/blog/2016/01/18/automatic-unflattening-with-automapper
However I'd prefer to be explicit with my mapping:
CreateMap<Restaurant, RestaurantInfo>().ForMember(info => info.Address,
expression => expression.MapFrom(restaurant => new Address
{
Address1 = restaurant.AddressAddress1,
Address2 = restaurant.AddressAddress2,
City = restaurant.AddressCity,
Lat = restaurant.AddressLat,
Long = restaurant.AddressLong,
ZipCode = restaurant.AddressZipCode
}));
I'm having a little difficulty mapping a domain model to a view model, using AutoMapper.
My controller code is:
//
// GET: /Objective/Analyst
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM();
ovm.DatePeriod = new DateTime(2013, 8,1);
var objectives = db.Objectives.Include(o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm.ObList = Mapper.Map<IList<Objective>, IList<ObjectiveVM>>(objectives);
return View(ovm);
}
I am getting an error on the ovm.ObList = Mapper.... (ObList is underlined in red with the error):
'ObList': cannot reference a type through an expression; try 'Objectives.ViewModels.ObjectiveVM.ObList' instead
My Objective Class is:
public class Objective
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public Analyst Analyst { get; set; }
}
My ObjectiveVM (view model) is:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
In my startup/global.asax.cs I have used AutoMapper to map the Objective to the ObjectiveVM:
Mapper.CreateMap<Objective, ObjectiveVM.ObList>()
.ForMember(dest => dest.Include, opt => opt.Ignore())
.ForMember(dest => dest.AnalystName, opt => opt.MapFrom(y => (y.Analyst.AnalystName)));
Any help would be much appreciated,
Mark
Ok, thanks for all the suggestions - what I've ended up with is:
Controller:
//
// GET: /Objective/Analyst
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM().obList;
var objectives = db.Objectives.Include(o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm = Mapper.Map<IList<Objective>, IList<ObjectiveVM.ObList>>(objectives);
var ovm2 = new ObjectiveVM();
ovm2.obList = ovm;
ovm2.DatePeriod = new DateTime(2013, 8,1);
return View(ovm2);
}
ViewModel:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
CreateMap:
Mapper.CreateMap<Objective, ObjectiveVM.ObList>()
.ForMember(dest => dest.Include, opt => opt.Ignore())
.ForMember(dest => dest.AnalystName, opt => opt.MapFrom(y => (y.Analyst.AnalystName)))
;
If I've mis-understood any advice, and you provided the answer, please post it - and I'll mark it as such.
Thank you,
Mark
As the commenter nemesv has rightly mentioned, the problem is about
ovm.ObList = Mapper.Map<IList<Objective>, IList<ObjectiveVM>>(objectives);
ObList is not a member of ObjectiveVM so, you should change the ObjectiveVM like this:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
Update:
Controller:
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM { DatePeriod = new DateTime(2013, 8, 1) };
var objectives = db.Objectives.Include(
o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm.obList = Mapper.Map<IList<Objective>,
IList<ObjectiveVM.ObList>>(objectives);
return View(ovm);
}