I have a CarDto and Car domain object.
Car has list of Drivers.
public class Car
{
public int Id {get; set; }
public int Name {get; set; }
public ICollection<Driver> Drivers {get; set; }
}
public class CarVM
{
public int Id {get; set; }
public int Name {get; set; }
public string DriverBadge {get; set; }
public string[] Drivers {get; set; }
}
I'm trying to map this objects using AutoMapper
CreateMap<CarVM, Car>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.Drivers, opts => {
... how to map here?
})
All you have to do is map from string[] to List<>. Simply use the below
CreateMap<CarVM, Car>()
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.Drivers, opts => opts.MapFrom(s=> s.Drivers.ToList()))
});
More information - Automapper Lists & Arrays
Related
I have two entity models in my ASP.NET Core 6 application:
public partial class Employee
{
public int Id { get; set; }
public string SerialNumber { get; set; } = null!;
public bool Active { get; set; }
public int? FKIdClassEmployee { get; set; }
public virtual ClassEmployee? ClassEmployee { get; set; }
}
public partial class ClassEmployee
{
public ClassEmployee()
{
Employee = new HashSet<Employee>();
}
public int Id { get; set; }
public string? Label { get; set; }
public decimal? Cost { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
In the EmployeeController, I'm try to use AutoMapper to map ClassEmployee to an existing record.
This is my Automapper config:
public class EmployeeProfile : Profile
{
private readonly ApplicationContext _ctx;
public EmployeeProfile(ApplicationContext ctx)
{
_ctx = ctx;
EmployeeDTOToEmployee();
}
private void EmployeeDTOToEmployee()
{
CreateMap<EmployeeDTO, Employee>()
.ForMember(dest => dest.SerialNumber, opt => opt.MapFrom(d => d.SerialNumber))
.ForMember(dest => dest.Active, opt => opt.MapFrom(d => d.Active))
.ForMember(dest => FKIdClassEmployee, opt => opt.MapFrom(d => d.FKIdClassEmployee))
.ForMember(dest => dest.ClassEmployee, opt => opt.MapFrom(
src => _ctx.ClassEmployee.Find(src.FKIdClassEmployee)));
}
}
But it doesn't work because it returns an error
Duplicate key value violates unique constraint
when it is called the Create method.
First of all involving DbContext in mapping configuration isn't a good idea (in my opinion). Use Lazy, Eager or Explicit loading instead. If a pair of Model (Entity) and Dto has same property name and type you don't have to specify .ForMemeber(dest => dest..., opt => opt.MapFrom(...)) . For nested mapping just configure all Entities and Dtos.
Configuration should look like this:
// NOTE: Same property type and name will be mapped
// Map Dto values to Entity
CreateMap<EmployeeDTO, Employee>();
CreateMap<ClassEmployeeDTO, ClassEmployee>();
// Map Entity values to Dto
CreateMap<Employee, EmployeeDTO>();
CreateMap<ClassEmployee, ClassEmployeeDTO>();
I have a small problem regarding mapping models of a external service.
The external service model:
public class Invoice
{
[...]
public int FeeId {get; set;}
public string FeeName {get; set;}
public int SubFeeId {get; set;}
public string SubFeeName {get; set;}
}
Trying to map to:
public class Invoice
{
[...]
public Fee Fee {get; set;}
public Fee SubFee {get; set;}
}
public class Fee
{
public int Id {get; set;}
public string Name {get; set;}
}
So I have my problem with the profile since I cannot a duplicate config because of different properties:
internal class InvoiceProfile : Profile
{
public InvoiceProfile()
{
CreateMap<data.Invoice, Invoice>()
[...]
.ForMember(dest => dest.Fee, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.SubFee, opt => opt.MapFrom(src => src));
CreateMap<data.Invoice, Fee>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.FeeId))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.FeeName));
CreateMap<data.Invoice, Fee>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.SubFeeId))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.SubFeeName));
}
}
How can i do this type of mapping?
I have a simple objects:
public class Project : Entity
{
public uint ProjectId { get; set; }
public virtual ICollection<Cabin> Cabins { get; set; }
}
public class Cabin : Entity
{
public IPAddress IpAddress { get; set; }
public int Port { get; set; }
public DateTime LastConnection { get; set; }
public byte ConnectionStatus { get; set; }
public byte TechnicalStatus { get; set; }
public Project Project { get; set; }
public int ProjectId { get; set; }
}
So mapping using auto mapper from one to another with some ignores would look like:
var mapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Project, Project>()
.ForMember(source => source.Id, opt => opt.Ignore())
.ForMember(source => source.ProjectId, opt => opt.Ignore())
.ForMember(source => source.Cabins, opt => opt.MapFrom(cab => cab.cabins));
});
And it works it maps one project object to another, and ignores id and project id and maps collection.
But on that level, is it possible to set what properties from source.Cabins would be ignored?
For example i want to ignore ConnectionStatus, TechnicalStatus.
You could add a configuration mapping for Cabin entity and AutoMapper would look at these configurations before mapping Cabin entity.
cfg.CreateMap<Cabin, Cabin>()
.ForMember(source => source.ConnectionStatus, opt => opt.Ignore())
.ForMember(source => source.TechnicalStatus, opt => opt.Ignore());
Or you could use AfterMap event to define a default value for these properties.
public class MyProfile : Profile
{
protected override void Configure()
{
base.CreateMap<ViewModel, Domain>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
//.ForAllMembers(opt => opt.Ignore()) //returns void
.ReverseMap();
}
}
public class ViewModel
{
public int Id { get; set; }
}
public class Domain
{
public int Id { get; set; }
public string UserName {get; set;}
//public string ... { get; set;} //etc..
//...
}
Suppose I don't want to map UserName, and many other properties.
Can I do .ForAllMembers(...) to the mapping, in order to map any unmapped members?
Don't use that ForAllMembers thing, that looks like a version of this:
https://github.com/AutoMapper/AutoMapper/wiki/5.0-Upgrade-Guide#ignoreallnonexisting-extension
Instead, use the CreateMap overload that takes a MemberList enum:
CreateMap<ViewModel, Domain(MemberList.None)
I am using C# EF code-first. I have the following 2 classes:
public class Om_Currency
{
[Key]
public Int16 CurrencyID { get; set; }
public String CurrencySymbol { get; set; }
public Boolean IsActive { get; set; }
public Int32 CountryID { get; set; }
public virtual Om_Country Country { get; set; }
}
public class Om_Country
{
[Key]
public Int16 CountryID { get; set; }
public String CountryName { get; set; }
public Boolean IsActive { get; set; }
public Int32 CurrencyID { get; set; }
public virtual Om_Currency Currency { get; set; }
}
Now, I am trying to implement an 1-1 relationship between these 2 classes. So that I can get Currency details from Country and Country details can be fetched from Currency.
modelBuilder
.Entity<Om_Country>()
.HasOptional(f => f.Currency)
.WithRequired(s => s.Country);
modelBuilder
.Entity<Om_Currency>()
.HasOptional(f => f.Country)
.WithRequired(s => s.Currency);
But I get this error:
The navigation property 'Country' declared on type
'ObjectModel.Country.Om_Currency' has been configured with conflicting
multiplicities.
Am I doing something wrong?
This is the mapping class for Country:
public class CountryMap : EntityTypeConfiguration<Om_Country>
{
public CountryMap()
{
Property(x => x.CountryID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.CountryName)
.IsRequired()
.HasMaxLength(100)
.HasColumnAnnotation
(
IndexAnnotation.AnnotationName,
new IndexAnnotation
(
new IndexAttribute("U_CountryName", 1) { IsUnique = true }
)
);
Property(x => x.IsActive).IsRequired();
Property(x => x.CurrencyID).IsRequired();
ToTable(clsCommon.tblCountry);
}
}
and this is the mapping class for Currency:
public class CurrencyMap : EntityTypeConfiguration<Om_Currency>
{
public CurrencyMap()
{
Property(x => x.CurrencyID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.CurrencySymbol)
.IsRequired()
.IsVariableLength()
.HasMaxLength(50)
.HasColumnAnnotation
(
IndexAnnotation.AnnotationName,
new IndexAnnotation
(
new IndexAttribute("U_CurrencySymbol", 1) { IsUnique = true }
)
);
Property(x => x.IsActive).IsRequired();
Property(x => x.CountryID).IsRequired();
ToTable(clsCommon.tblCurrency);
}
}
I figured out the issue. I got the Solution from here
Instead of below
modelBuilder
.Entity<Om_Country>()
.HasOptional(f => f.Currency)
.WithRequired(s => s.Country);
modelBuilder
.Entity<Om_Currency>()
.HasOptional(f => f.Country)
.WithRequired(s => s.Currency);
It should be below
modelBuilder.Entity<Om_Country>()
.HasRequired(x => x.Currency).WithMany()
.HasForeignKey(x => x.CurrencyID).WillCascadeOnDelete(false);
modelBuilder.Entity<Om_Currency>()
.HasRequired(x => x.Country).WithMany()
.HasForeignKey(x => x.CountryID).WillCascadeOnDelete(false);