EF Core with AutoMapper - automapper

I have a DTO containing properties and a Model, eg student can have more than one module and a module can be associated with more than one student. the properties are mapping fine but the Model doesn't map.
public class GetStudentByIdMapping : Profile
{
public GetStudentByIdMapping()
{
CreateMap<Student,StudentDetails>();
CreateMap<Module, StudentDetails>()
.ForPath(dest => dest.StudentModules.ModuleName, opt => opt.MapFrom(m => m.ModuleName))
.ForPath(dest => dest.StudentModules.ModuleCode, opt => opt.MapFrom(m => m.ModuleCode))
.ForPath(dest => dest.StudentModules.Description, opt => opt.MapFrom(m => m.Description))
.ReverseMap();
}
}
public async Task<StudentDetails> GetStudent(int studentId)
{
var student = context.Student
.Where(s => s.StudentId == studentId)
.FirstOrDefault();
var module = await context.Order
.Include(m => m.Module)
.Where(o => o.StudentId == studentId)
.Select(m => m.Module).ToListAsync();
var studMap = Mapper.Map<StudentDetails>(student);
Mapper.Map<StudentDetails>(module);
return studMap;
}
These are the ViewModels I want to map to the Models Model in the StudentDetails ViewModel
public class StudentDetails
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public StudentModule StudentModules { get; set; }
}
public class StudentModule
{
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
}
These are my Entities generated by EF Core
public partial class Student
{
public Student()
{
Order = new HashSet<Order>();
}
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Order> Order { get; set; }
}
public partial class Module
{
public Module()
{
Order = new HashSet<Order>();
}
public int ModuleId { get; set; }
public int? LectureId { get; set; }
public string ModuleName { get; set; }
public string ModuleCode { get; set; }
public string Description { get; set; }
public string ModulePath { get; set; }
public virtual Lecture Lecture { get; set; }
public virtual ICollection<Order> Order { get; set; }
}

You only need to pass the created destination to the second map call:
Just try the following code when mapping:
var studMap = Mapper.Map<Student,StudentDetails>(student);
Mapper.Map<Module,StudentDetails>(module,studMap);
Then the studMap will receive all mapped fields value.

Related

Automapper flatten from three levels to two levels

I use automapper v. 9 and I need to transform a 3 levels structure to a 2 levels structure.
I already tried with the IncludeMembers option, but I can't achieve the result I want.
Here is my model classes (simplified)
public partial class Mail
{
public Mail()
{
Mail_Files = new HashSet<Mail_Files>();
}
[Key]
public int Id { get; set; }
public string Oggetto { get; set; }
public string Testo { get; set; }
[InverseProperty("IdMailNavigation")]
public virtual ICollection<Mail_Files> Mail_Files { get; set; }
}
public partial class Mail_Files
{
[Key]
public int Id { get; set; }
public int IdMail { get; set; }
public int IdFile { get; set; }
[ForeignKey(nameof(IdFile))]
[InverseProperty(nameof(Files.Mail_Files))]
public virtual Files IdFileNavigation { get; set; }
[ForeignKey(nameof(IdMail))]
[InverseProperty(nameof(Mail.Mail_Files))]
public virtual Mail IdMailNavigation { get; set; }
}
public partial class Files
{
public Files()
{
Mail_Files = new HashSet<Mail_Files>();
}
[Key]
public int Id { get; set; }
public int IdTipoFile { get; set; }
public string FileName { get; set; }
public int? FileSize { get; set; }
[InverseProperty("IdFileNavigation")]
public virtual ICollection<Mail_Files> Mail_Files { get; set; }
}
I have to flatten this object to this:
public class MailFileResource
{
public int Id { get; set; }
public string FileName { get; set; }
}
public class MailResource
{
public int Id { get; set; }
public string Testo { get; set; }
public IEnumerable<MailFileResource> Files { get; set; }
}
I do it this way:
CreateMap<Files, MailFileResource>()
.ForMember(d => d.FileName, opt => opt.MapFrom(s => s.FileName))
.ForMember(d => d.Id, opt => opt.MapFrom(s => s.Id));
CreateMap<Mail_Files, MailFileResource>().IncludeMembers(s => s.IdFileNavigation);
CreateMap<Mail, MailResource>()
.ForMember(d => d.Files , opt => opt.MapFrom(s => s.Mail_Files))
.ForMember(d => d.Protezione, opt => opt.MapFrom(s => s.Protezione));
My mistake was to use IncludeMembers in Mail -> MailResource mapping too

Configuring AutoMapper in ASP.NET Core

I am trying to use automapper 8.0.0 to fill a list of WorkViewModel.
This list is getting data from the Work class out of the database using entity framework.
How ever it looks like something is going wrong with initializing as throwing follows error:
InvalidOperationException: Mapper not initialized
What am I doing wrong?
I have setup the following code!
Startup.cs
services.AddAutoMapper();
Function being called:
public async Task<IEnumerable<WorkViewModel>> GetAllAsync()
{
IList<Work> works = await _context.Work.ToListAsync();
IList<WorkViewModel> viewModelList = Mapper.Map<IList<Work>, IList<WorkViewModel>>(works);
return viewModelList;
}
Configuration:
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<WorkViewModel, Work>();
});
}
}
WorkViewModel:
public class WorkViewModel
{
public int WorkID { get; set; }
public string Name { get; set; }
public byte[] Tumbmail { get; set; }
public string Discription { get; set; }
public string Client { get; set; }
public DateTime Date { get; set; }
public string PreviewLink { get; set; }
public string GitLink { get; set; }
public string DownloadLink { get; set; }
public int DetailID { get; set; }
public byte[] Banner { get; set; }
public string Documentation { get; set; }
public int CategoryID { get; set; }
public string Category { get; set; }
}
Work Model:
public class Work
{
[Key]
public int WorkID { get; set; }
[Display(Name = "Project Name")]
public string Name { get; set; }
[Display(Name = "Client name")]
public string Client { get; set; }
[Display(Name = "Description")]
public string Discription { get; set; }
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Display(Name = "Thumbmail")]
public byte[] Tumbmail { get; set; }
[Display(Name = "Preview Link")]
public string PreviewLink { get; set; }
[Display(Name = "Git Link")]
public string GitLink { get; set; }
[Display(Name = "DownloadLink")]
public string DownloadLink { get; set; }
public WorkCategory workCategory { get; set; }
public WorkDetailed WorkDetailed { get; set; }
}
Only adding services.AddAutoMapper(); to the ConfigureServices method would not work for you. You have to configure AutoMapper as follows:
public void ConfigureServices(IServiceCollection services)
{
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}
And also don't forget to install the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package.

Automapper to map child tables

I have 3 tables in my database and these are the classes:
public class PRAT
{
public PRAT()
{
PRAT_RIC = new HashSet<PRAT_RIC>();
}
public int ID { get; set; }
public string PRATICA { get; set; }
public int ANNO { get; set; }
public string VARIANTE { get; set; }
[...] other fields
}
public class PRAT_RIC
{
public int ID { get; set; }
public int IDPRAT { get; set; }
public int IDANAG { get; set; }
public int? IDTITRIC { get; set; }
public virtual ANAGRAFI IDANAGNavigation { get; set; }
}
public class ANAGRAFI
{
public int ID { get; set; }
public string TIPO { get; set; }
public string TITOLO { get; set; }
public string COGNOME { get; set; }
public string NOME { get; set; }
public string CODFISC { get; set; }
}
So, the relations are:
Prat -> Prat_Ric (Prat.Id=Prat_Ric.IdPrat) -> Anagrafi (Anagrafi.Id=Prat_Ric.IdAnag)
These are the destination classes:
public class PraticaResource
{
public int ID { get; set; }
public string PRATICA { get; set; }
public int ANNO { get; set; }
public string VARIANTE { get; set; }
public ICollection<RichiedentiResource> RICHIEDENTI { get; set; }
}
public class RichiedentiResource
{
public int ID { get; set; }
public int IDANAG { get; set; }
public string TITOLO { get; set; }
public string COGNOME { get; set; }
public string NOME { get; set; }
public string CODFISC { get; set; }
public string TITRIC { get; set; }
}
I created the map with
CreateMap<PRAT_RIC, RichiedentiResource>()
.ForMember(p => p.COGNOME, opt => opt.MapFrom(ti => ti.IDANAGNavigation.COGNOME))
.ForMember(p => p.NOME, opt => opt.MapFrom(ti => ti.IDANAGNavigation.NOME));
Is it possible to map all the properties of RichiedentiResource to the ANAGRAFI fields with the same name so that i haven't to write every single field mapping?
Thank you.
If you don't need anything from the navigation class, then you can just skip it when mapping at the top level:
CreateMap<PRAT, PraticaResource>()
.ForMember(p => p.PRAT_RIC, opt => opt.MapFrom(src => src.PRAT_RIC.Select(x => x.IDANAGNavigation))
Then you just need the mapping from ANAGRAFI -> RichiedentiResource, which should work out the field mappings automatically.

Complex Automapper Configuration

I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}

Entity Framework - Update complex entities

I'm using EF5.0 (database first) and trying to Update "Company" entity which is a complex type, it contains "Address" entity as navigation property.
I receive a Company DTO object from UI and I map it, using AutoMapper, to Entity object and call objectContext.Save() for saving.
Problem am facing is, "Company" entity values are getting saved but not the "Address" entity. Below are the each object details-
public class CompanyDto
{
public int Id { get; set; }
public string Name { get; set; }
public AddressDto Address { get; set; }
}
with AddressDto as -
public class AddressDto : IDto
{
public int Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string PostCode { get; set; }
}
Company Entity (generated by EF - database first)
public partial class tblCompany
{
public tblCompany()
{
this.tblAddresses = new HashSet<tblAddress>();
}
public int ID { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<tblAddress> tblAddresses { get; set; } //navigation property
}
with Address entity is as follows -
public partial class tblAddress
{
public int ID { get; set; }
public int CaseID { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public virtual tblCase tblCase { get; set; }
}
AutoMapper mapping configuration for converting from DTO to entity
Mapper.CreateMap<CompanyDto, tblCase>()
.ForMember(x => x.ID, opt => opt.MapFrom(cd => cd.Id))
.ForMember(x => x.CompanyName, opt => opt.MapFrom(cd => cd.Name))
.AfterMap((s, d) => d.tblAddresses.Add(new tblAddress
{
AddressLine1 = s.Address.Street,
CaseID = s.Id,
City = s.Address.City,
PostCode = s.Address.PostCode
}));
public void Update(CompanyDto company)
{
//TO DO: check if AutoMapper could map address as well.
var companyDao = Mapper.Map<CompanyDto, tblCase>(company);
_companyRepository.Update(companyDao);
_unitOfWork.Save();
}
Thanks in advance
Sai

Resources