EF Core and Automapper. Mapping relationship one-to-many - asp.net-core

I'm currently using AutoMapper on an Project running code-first Entity Framework.
Here just simple entities and DTO's:
// DTO Profile
public class CreateOrEditProfileDto
{
public string Code { get; set; }
public string Name { get; set; }
public List<RouteDto> Routes { get; set; }
}
// entity Profile
public class Profile
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
}
// DTO Route
public class RouteDto
{
public string DriverName { get; set; }
public string DriverSurname { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public int ProfileId { get; set; }
}
//entity Route
public class Route
{
public virtual string DriverName { get; set; }
public virtual string DriverSurname { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual int ProfileId { get; set; }
[ForeignKey("ProfileId")]
public Profile ProfileFk { get; set; }
}
//Mapper
configuration.CreateMap<RouteDto, Route>().ReverseMap();
// configuration.CreateMap<CreateOrEditProfileDto, Profile>().ReverseMap();
// this type of configuration give the error written below
configuration.CreateMap<CreateOrEditProfileDto, Profile>()
.ForMember(dest => dest, opt =>
opt.MapFrom(src => src.Routes.Select(x =>
new Route()
{
ProfileId = x.ProfileId,
DriverName = x.DriverName,
DriverSurname = x.DriverSurname,
Phone = x.Phone,
Email = x.Email,
}
)
)
);
I'm a little bit confusing, I'm trying to map one-to-many relationship between Profile and Route, Route has a foreign key to Profile. A single profile could have more routes. So, I want to create a profile and attach the list of routes, but when I compile the solution, I get this error:
AutoMapper.AutoMapperConfigurationException: 'Custom configuration for members is only supported for top-level individual members on a type.'
Does anyone know the best way to solve this mapping?
Regards

Because List<RouteDto> is mapped to Profile, the type does not match. You need to add a property in Profile.
public class Profile
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
public List<Route> Routes { get; set; }
}
The mapping attribute dest.Routes need to be specified. Then, the Routes will be automatically mapped.
CreateMap<CreateOrEditProfileDto, EntityProfile>()
.ForMember(dest => dest.Routes, opt =>
opt.MapFrom(src => src.Routes.Select(x=>
new Route()
{
ProfileId = x.ProfileId,
DriverName = x.DriverName,
DriverSurname = x.DriverSurname,
Phone = x.Phone,
Email = x.Email,
}
))
);

Related

Problem with mapping two objects (with lists)

I am looking for solution my issue... Probably my Shifts class cannot be mapped.
I have entity class Worker:
public class Worker
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(200)]
public string PhotoFilePath { get; set; }
public Workplace Workplace { get; set; }
public int WorkplaceId { get; set; }
public List<Service> Services { get; set; }
public List<Shift> Shifts { get; set; }
public IEnumerable<Worker> ToList()
{
throw new NotImplementedException();
}
}
And model WorkerModel:
public int Id { get; set; }
[Required]
[DisplayName("Imię")]
public string Name { get; set; }
[DisplayName("Nazwisko")]
public string LastName { get; set; }
[Display(Name = "Zdjęcie")]
public IFormFile Photo { get; set; }
public string PhotoFilePath { get; set; }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int WorkplaceId { get; set; }
public List<ServiceModel> Services { get; set; }
public List<ShiftModel> Shifts { get; set; }
}
My default mapper profile:
//Mapping workers
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>();
And when I try map model to entity class in my action:
Worker worker = _mapper.Map<Worker>(model);
I get an issue:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
This is caused by different mapping types. Take the property Service as an example.
The resource is a type of Service.
But the destination is a type of ServiceModel.
So, they need to be converted. Here is a demo.
I create the Service and ServiceModel according to your model.
public class Service
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
public class ServiceModel
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
This is mapping relationship.
public class AutomapProfile : Profile
{
public AutomapProfile()
{
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>()
.ForMember(m => m.Services, x => x.MapFrom(y => y.Services.Select(a=>
new Service
{
serviceID=a.serviceID,
myservice=a.myservice
})));
}
}
This is the mapping method.
public IActionResult Index()
{
var model = new WorkerModel
{
Id=1,
Name="names",
//...
Services = new List<ServiceModel>
{
new ServiceModel{ serviceID=1, myservice="service1"},
new ServiceModel{ serviceID=2, myservice="service2"},
},
//...
};
Worker worker = _mapper.Map<Worker>(model);
return Ok(worker);
}
Result.

The problem with Include method using on Db context - Asp.Net Core

I'v got a backend on Asp.Net Core. Structure of the database looks that:
User - the basics information about user: login, password etc.
Profile - this entity is connected to the"User" one to one relation
Profile photos- each of the users has a own collection of photos.
This entity is connected to the "Profile"
Here is the "User" entity:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public Profile Profile { get; set; }
}
Then Profile:
public class Profile
{
[ForeignKey("User")]
public int Id { get; set; }
public string BannerImageUrl { get; set; }
public string ProfileImageUrl { get; set; }
public string ShortDescription { get; set; }
public string Description { get; set; }
public User User { get; set; }
public ICollection<ProfilePhotos> ProfilePhotos { get; set; }
}
And "ProfilePhotos":
public class ProfilePhotos
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public int ProfileId { get; set; }
public Profile Profile { get; set; }
}
I want to get all profile photos so I created a endpoint to to that:
[HttpGet("{username}/photos")]
public IActionResult GetPhotos(string username)
{
var profilePhotos = _profileService.GetAllPhotos(username);
var model = _mapper.Map<IList<ProfilePhotosModel>>(profilePhotos);
return Ok(model);
}
To get all photos I use a method from "profileService":
public IEnumerable<ProfilePhotos> GetAllPhotos(string username)
{
return _context.ProfilePhotos.Include(a=>a.Profile).ThenInclude(b=>b.User).Where(x => x.Profile.User.Username == username);
}
On response I want to get a id of photo, photoUrl and username so I mapped my profile photos to "ProfilePhotosModel"
public class ProfilePhotosModel
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public string Username { get; set; }
}
but unfortunately on response I only get Id and photoUrl. The username is null :(
What am I doing wrong?
You could add custom mapping for the Username property.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ProfilePhotos, ProfilePhotosModel>()
.ForMember(m => m.Username, exp => exp.MapFrom(p => p.Profile.User.Username));
});

map string filed to generic list in automapper based on .net core

I have a DomainModel and a DTO like this :
public class PostEntity: IEntity
{
[Required]
public string Description { get; set; }
public int Id { get; set; }
[Required]
public string Slug { get; set; }
[Required]
public string Tags { get; set; }
[Required]
public string Title { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public PostStatus Status { get; set; }
public User Writer { get; set; }
public int WriterId { get; set; }
}
public class PostDto
{
public int Id { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public string Tags { get; set; }
public string Title { get; set; }
public DateTime CreatedOn { get; }
public List<string> TagList { get; set; }
public PostDto()
{
TagList = new List<string>();
}
}
PostEntity'Tags contains some tags seperated by ",", now I want to split tags value by "," and convert it to List, to do this, I've tried this but I get the below compilation error
CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(",").ToList()));
I get this error :
An expression tree may not contain a call or invocation that uses optional arguments
I can't reproduce your error, it seems to work fine.
Below is an example where the TagList is correctly mapped
The code I used :
MapperConfiguration MapperConfiguration = new MapperConfiguration(configuration =>
{
configuration
.CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(',').ToList()));
});
IMapper mapper = MapperConfiguration.CreateMapper();
PostEntity postEntity = new PostEntity
{
Tags = "Tag1,Tag2,Tag3,Tag4"
};
var mappedObject = mapper.Map<PostEntity, PostDto>(postEntity);
Please bear in mind that Expression.Call API does not support optional parameters. So, you should Replace Split(',') with
Split(',', System.StringSplitOptions.None)
or
Split(',', System.StringSplitOptions.RemoveEmptyEntries)
doing so you won't see that error again.

Automapper ConstructUsing not working as expected

I am using automapper in my asp.net core project and it's my first time with that library. The data flow is as follows: Model->DomainModel->ViewModel. Automapper is used for mapping between those. I have problems using ConstructUsing. It seems to me it is not working.
Part of the data profile class:
CreateMap<ClinicD, ClinicViewModel>()
.ConstructUsing(x => new ClinicViewModel
{
Active = x.Active,
CooperationStart = x.CooperationStart,
CooperationEnd = x.CooperationEnd,
Id = x.Id,
Name = x.Name,
AddressId = x.Address.Id,
FlatNo = x.Address.FlatNo,
City = x.Address.City,
HouseNumber = x.Address.HouseNumber,
Street = x.Address.Street,
Postcode = x.Address.Postcode
})
.ForMember(x => x.ChosenSpecialities, opt => opt.Ignore())
.ForMember(x => x.Specialities, opt => opt.Ignore());
public class ClinicViewModel : CooperationSpotViewModel
{
private IEnumerable<int> _chosenSpecialities;
public IEnumerable<SelectListItem> Specialities
{
get;set;
}
public IEnumerable<int> ChosenSpecialities
{
get
{
if (_chosenSpecialities == null)
_chosenSpecialities = new List<int>();
return _chosenSpecialities;
}
set
{
if (value != null)
_chosenSpecialities = value;
}
}
}
public abstract class CooperationSpotViewModel : BaseViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
public DateTime? CooperationStart { get; set; }
public DateTime? CooperationEnd { get; set; }
public bool? Active { get; set; }
public string PhoneNumber { get; set; }
public int? AddressId { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string FlatNo { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
}
public class ClinicD : CooperationSpotD
{
public IEnumerable<SpecialityD> Specialities
{
get;set;
}
}
public abstract class CooperationSpotD
{
public int? Id { get; set; }
public string Name { get; set; }
public DateTime? CooperationStart { get; set; }
public DateTime? CooperationEnd { get; set; }
public bool? Active { get; set; }
public string PhoneNumber { get; set; }
public AddressD Address { get; set; }
}
A similar issue occurs for me in several spots, so I am guessing, I must be doing something basic wrong. The exception that occurs:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
ClinicD -> ClinicViewModel (Destination member list)
SeeingEyeDog.BusinessLogic.Models.ClinicD -> SeeingEyeDog.Models.ClinicViewModel (Destination member list)
Unmapped properties:
Street
HouseNumber
FlatNo
City
Postcode

How to disable changestate tracking for a sub-entity in boilerplate AppService

I'm using aspnet core & ef core with boilerplate and would like to disable the changestate tracking for a sub-entity. How do I this this within AppService (ie AsyncCrudAppService).
For example:
Entity:
[Table("tblCategory")]
public class Category : FullAuditedEntity<int>, IMustHaveTenant
{
public const int MaxCodeLength = 128;
public const int MaxNameLength = 2048;
public virtual int TenantId { get; set; }
[Required]
[StringLength(MaxCodeLength)]
public virtual string Code { get; set; }
[Required]
[StringLength(MaxNameLength)]
public virtual string Name { get; set; }
[ForeignKey("GroupId")]
public virtual Group Group { get; set; }
[Required]
public virtual int GroupId { get; set; }
}
Dto:
[AutoMapFrom(typeof(Category))]
public class CategoryDto : FullAuditedEntityDto<int>
{
[Required]
[StringLength(Category.MaxCodeLength)]
public string Code { get; set; }
[Required]
[StringLength(Category.MaxNameLength)]
public string Name { get; set; }
[Required]
public int GroupId { get; set; }
[DisableValidation]
public GroupDto Group{ get; set; }
}
AppService Update Method:
public override async Task<CategoryDto> Update(CategoryDto input)
{
var cat = await _categoryManager.GetA(input.Id);
MapToEntity(input, cat);
//I'd like to disable the tracking of cat.Group here ?
await _categoryManager.UpdateA(cat);
return await Get(input);
}
I'd like to disable the change detection for cat.Group, how do I do this ?
Thanks in advance.
Using AsNoTracking when loading resolves the issue
Tracking can be skipped by adding .AsNoTracking() to the call.
For example:
var cat = await _yourDbContext.AsNoTracking().FirstAsync(m => m.Id == input.Id);
Which is fine for results that will not be edited during it's lifetime.