AutoMapper Update UseDestinationValue Not Working as expected on Virtual Properties - c#

Struggling to get AutoMapper (6.1.1) to work in this scenario of attempting to update an existing Vendor and associated Vendor Contacts.
I've tried using .ignore() and .UseDestinationValues() on the related entities, both to no avail.
Here's what happens to the destination values after the map:
existingStratusVendor.Id = 0 (should be value of existing)
existingStratusVendor.VendorContacts.Id = 0 (should be value of existing)
existingStratusVendor.Items = null, but had 1 related entity prior to mapping, same with all other related virtual properties. (this happens for all other virtual properties that I've marked as .UseDestinationValues() as well)
What am I doing wrong or am I misunderstanding how this is supposed to work?
POCOs
public partial class Vendor
{
public Vendor()
{
this.Items = new HashSet<Item>();
this.Items1 = new HashSet<Item>();
this.VendorContacts = new HashSet<VendorContact>();
this.POHeaders = new HashSet<POHeader>();
this.ReceiptHeaders = new HashSet<ReceiptHeader>();
this.ItemPriceCostRules = new HashSet<ItemPriceCostRule>();
}
public int Id { get; set; }
public int CompanyId { get; set; }
public string VendorName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Notes { get; set; }
public int CreatedById { get; set; }
public System.DateTime CreatedOn { get; set; }
public int ModifiedById { get; set; }
public System.DateTime ModifiedOn { get; set; }
public string FinancialsId { get; set; }
public int LeadTimeDays { get; set; }
public int SafetyStockDays { get; set; }
public virtual ICollection<Item> Items { get; set; }
public virtual ICollection<Item> Items1 { get; set; }
public virtual ICollection<VendorContact> VendorContacts { get; set; }
public virtual ICollection<POHeader> POHeaders { get; set; }
public virtual Company Company { get; set; }
public virtual UserProfile UserProfile { get; set; }
public virtual UserProfile UserProfile1 { get; set; }
public virtual ICollection<ReceiptHeader> ReceiptHeaders { get; set; }
public virtual ICollection<ItemPriceCostRule> ItemPriceCostRules { get; set; }
}
public partial class VendorContact
{
public int Id { get; set; }
public int VendorId { get; set; }
public string ContactName { get; set; }
public string EmailAddress { get; set; }
public string OfficePhone { get; set; }
public string CellPhone { get; set; }
public int CreatedById { get; set; }
public System.DateTime CreatedOn { get; set; }
public int ModifiedById { get; set; }
public System.DateTime ModifiedOn { get; set; }
public bool PurchasingContact { get; set; }
public virtual Vendor Vendor { get; set; }
public virtual UserProfile UserProfile { get; set; }
public virtual UserProfile UserProfile1 { get; set; }
}
Maps
CreateMap<Vendor, Vendor>()
.ForMember(dest => dest.Id, option => option.UseDestinationValue())
.ForMember(dest => dest.Company, option => option.UseDestinationValue())
.ForMember(dest => dest.POHeaders, option => option.UseDestinationValue())
.ForMember(dest => dest.ReceiptHeaders, option => option.UseDestinationValue())
.ForMember(dest => dest.Items, option => option.UseDestinationValue())
.ForMember(dest => dest.Items1, option => option.UseDestinationValue())
.ForMember(dest => dest.ItemPriceCostRules, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile1, option => option.UseDestinationValue())
;
CreateMap<VendorContact, VendorContact>()
.ForMember(dest => dest.Id, option => option.UseDestinationValue())
.ForMember(dest => dest.VendorId, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile1, option => option.UseDestinationValue())
Code
public ActionConfirmation<int> ImportFromFinancials(Vendor financialsModifiedVendor, int intUserId)
{
Vendor vendorToUpdate;
var existingStratusVendor = _vendorRepository
.SearchFor(a => a.CompanyId == intCompanyId && a.FinancialsId == financialsModifiedVendor.FinancialsId).FirstOrDefault();
if (existingStratusVendor == null) //add a new vendor
{
vendorToUpdate = financialsModifiedVendor;
}
else
{
Mapper.Map(financialsModifiedVendor, existingStratusVendor);
vendorToUpdate = existingStratusVendor;
}
//Save Vendor
var baseAppServ = new BaseAppServ<Vendor>(_repository);
var vendorUpdateResult = baseAppServ.SaveOrUpdate(vendorToUpdate, intUserId);
if (!vendorUpdateResult.WasSuccessful) return vendorUpdateResult;
...
}

both entities has the same name, it looks like you are missing a namespace
CreateMap<Other.Namespace.VendorContact, VendorContact>()
.ForMember(dest => dest.Id, option => option.UseDestinationValue())
.ForMember(dest => dest.VendorId, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile, option => option.UseDestinationValue())
.ForMember(dest => dest.UserProfile1, option => option.UseDestinationValue())

Related

AutoMapper map a collection Property value from its Parent property

I have two models, Receipt.cs and ReceiptProduct.cs. What I want to achieve is to map the ICollection ReceiptProducts fields like PurchaseOrderId and ReceiptId from its parent Receipt.
Receipt.cs
public class Receipt
{
public Guid Id { get; set; }
public string Reference { get; set; }
public string PurchaseOrderId { get; set; }
public virtual ICollection<ReceiptProduct> ReceiptProducts { get; set; }
}
ReceiptProduct.cs
public class ReceiptProduct
{
public Guid Id { get; set; }
public string ReceiptId { get; set; }
public string PurchaseOrderId { get; set; }
public string ProductName { get; set; }
public string ProductId { get; set; }
public string Note { get; set; }
}
ReceiptProducts.ReceiptId <= Receipt.Id
ReceiptProducts.PurchaseOrderId <= Receipt.PurchaseOrderId
I tried the below code. But I got the error
CreateMap<DataEntities.Receipt, BusinessEntities.Receipt>()
.ForMember(dest => dest.ReceiptProducts.Select(x=>x.ReceiptId), automapper => automapper.MapFrom(src => src.Id));
Error : AutoMapper.AutoMapperConfigurationException: Custom configuration for members is only supported for top-level individual members on a type.
So how to map that collection property values.
try this.
public class ReceiptProduct
{
public Guid Id { get; set; }
public string ReceiptId { get; set; }
public string PurchaseOrderId { get; set; }
public string ProductName { get; set; }
public string ProductId { get; set; }
public string Note { get; set; }
**public Receipt Receipt { get; set; }**
}
Mapping
CreateMap<DataEntities.ReceiptProduct, BusinessEntities.Receipt>()
.ForMember(dest => x=>x.ReceiptId, opts => opts.MapFrom(src => src.Receipt.Id))
.ForMember(dest => x=>x.PurchaseOrderId , opts => opts.MapFrom(src => src.Receipt.PurchaseOrderId))
.ForMember(dest => x=>x.Reference , opts => opts.MapFrom(src => src.Receipt.Reference ));

How to select from a list and map the another object using Automapper

From the nested Entity list we want to select an object and map it in the Dto object, but when we use the Automapper to filter for object which deleteDate is equal null and map it, for each column Automapper filter again on db. how to filter one time and map selected to Dto object
We need to store all the information in our database so use DeleteDate column with default value null. so we use one to many relation to store history of our data.
public class User
{
public Guid UserId { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IList<CustomerDetail> CustomerDetails { get; set; }
}
public class CustomerDetail
{
public Guid CustomerDetailId { get; set; }
public float MaximumAmountPerTransaction { get; set; }
public float MaximumAmountPerDay { get; set; }
public bool IsConfirm { get; set; }
public Guid UserId { get; set; }
public virtual DateTimeOffset CreateDate { get; set; }
public virtual DateTimeOffset? DeleteDate { get; set; }
}
public class CustomerResponse
{
[Key]
public Guid CustomerId{ get; set; }
public string MobileNumber{ get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsConfirm { get; set; }
public float? MaximumAmountPerTransaction { get; set; }
public float? MaximumAmountPerDay { get; set; }
}
public class CustomerDomainProfile : Profile
{
public UserDomainProfile()
{
CreateMap<User, CustomerResponse>()
.ForMember(dest => dest.CustomerId,
opt => opt.MapFrom(src => src.UserId))
.ForMember(dest => dest.Country,
opt => opt.MapFrom(src => src.Country.CommonName))
.ForMember(dest => dest.IsConfirm,
opt => opt.MapFrom(src => src.CustomerDetails.FirstOrDefault(cd => cd.DeleteDate == null).IsConfirm))
.ForMember(dest => dest.MaximumAmountPerTransaction,
opt => opt.MapFrom(src => src.CustomerDetails.FirstOrDefault(cd => cd.DeleteDate == null).MaximumAmountPerTransaction))
.ForMember(dest => dest.MaximumAmountPerDay,
opt => opt.MapFrom(src => src.CustomerDetails.FirstOrDefault(cd => cd.DeleteDate == null).MaximumAmountPerDay))
}
}
I expect a query to the database and select the CustomerDetails with DeleteDate is null and map it, but the type of our define, for each column trying to query to database because
I expect that querying the database and selecting CustomerDetails with DeleteDate is null and its map, but our definition, for each column, is trying to query the database because it can not recognize that they are from a same table. How can we identify them that have fallen from a row to reduce the query? is any other solution or idea?

Mapping objects with item in source to list of items in the destination and RowVersion in the destination

I have several hours trying to map between the following types:
Source Type:
public class PatientModel : IPatientModel
{
public int Id { get; set; }
public int? PatientNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public int? Gender { get; set; }
public string Job { get; set; }
public byte[] RowVersion { get; set; }
public DateTime? LastVisitDate { get; set; }
}
Destination Type:
public class Patient : IConcurrencyAwareEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Index(IsUnique = true)]
public int? PatientNumber { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public int? Gender { get; set; }
/// <summary>
/// Inverse Prop.
/// </summary>
private ICollection<PatientJob> _jobs;
private ICollection<Visit> _visits;
public virtual ICollection<PatientJob> Jobs
{
get
{
if (_jobs == null)
this._jobs = new List<PatientJob>();
return _jobs;
}
set => _jobs = value;
}
public virtual ICollection<Visit> Visits
{
get
{
if (_visits == null)
this._visits = new List<Visit>();
return _visits;
}
set => _visits = value;
}
//Concurrency
public byte[] RowVersion { get; set; }
}
I use following automapper configuration
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<PatientProfile>();
});
mapperConfiguration.AssertConfigurationIsValid();
_mapper = mapperConfiguration.CreateMapper();
The Mapping profile as following
public class PatientProfile : Profile
{
public PatientProfile()
{
this.CreateMap<Patient, PatientModel>(MemberList.Destination)
.ForMember(pm => pm.LastVisitDate, opt => opt.MapFrom(p => p.Visits.AsEnumerable()
.OrderByDescending(f => f.Id).Select(v => v.VisitDate)
.FirstOrDefault()))
.ForMember(pm => pm.Job,
opt => opt.MapFrom(p =>
p.Jobs.AsEnumerable().OrderByDescending(f => f.Id).Select(a => a.Job).FirstOrDefault()));
this.CreateMap<PatientModel, Patient>(MemberList.Destination)
.ForMember(p => p.Id, opt => opt.Ignore())
.ForMember(p => p.Jobs, opt => opt.Ignore())
.ForMember(p => p.Visits, opt=>opt.Ignore())
.ForMember(p=>p.RowVersion, opt=>opt.Ignore());
}
I'm getting following exception
System.NullReferenceException
When I tried to map using following code:
PatientModel pm = new PatientModel();
pm.FirstName = "Anas";
pm.LastName = "Tina";
pm.BirthDate = new DateTime(1990, 1, 1);
var patient = _mapper.Map<Patient>(pm);
I searched the web, downloaded the AutoMapper documentation with no luck.

automapper : map one object to many

I have the following domain model (one class):
public class DriverDomain
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int? TruckId { get; set; }
public int? TrailerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? UnitNo { get; set; }
public int? GpsId { get; set; }
public string CompanyFEIN { get; set; }
public string CompanyName { get; set; }
public string CompanyAddress { get; set; }
public string CompanyCity { get; set; }
public string CompanyZIP { get; set; }
public string CompanyState { get; set; }
public System.DateTime? DOB { get; set; }
public string SSN { get; set; }
public string PhoneNo { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZIP { get; set; }
public string DLno { get; set; }
public string Dlstate { get; set; }
public System.DateTime? DLexp { get; set; }
public System.DateTime? MedExp { get; set; }
public System.DateTime? HireDate { get; set; }
public System.DateTime? TermDate { get; set; }
public bool Active { get; set; }
public bool DrugTest { get; set; }
public string Notes { get; set; }
public string CardNo { get; set; }
public string EmployeeNo { get; set; }
public bool? OwnerOp { get; set; }
public bool OccAcc { get; set; }
public decimal? WeeklyOccAcc { get; set; }
public bool Ifta { get; set; }
public decimal? WeeklyIfta { get; set; }
public bool TrailerRental { get; set; }
public decimal? WeeklyTrailerRent { get; set; }
public bool CargoIns { get; set; }
public decimal? WeeklyCargoIns { get; set; }
public decimal? PilotRebate { get; set; }
public bool OnlineAccess { get; set; }
public int? OnlineId { get; set; }
public bool ViewedSchedule { get; set; }
public int SchedulePriority { get; set; }
public bool Hourly { get; set; }
public decimal? HourlyPay { get; set; }
public string IpassTransponderId { get; set; }
public System.DateTime? RecordDate { get; set; }
public string RecordChangedBy { get; set; }
public string EmgcontactName { get; set; }
public string EmgcontactPhone { get; set; }
public string EmgcontactRelationship { get; set; }
public string Nickname { get; set; }
public string UserId { get; set; }
public string AspNetUserName { get; set; }
public string AvatarUrl { get; set; }
public bool PaidByPercent { get; set; }
public decimal? PercentPaid { get; set; }
public bool PaidByMile { get; set; }
public decimal? PayPerMile { get; set; }
public bool CompanyPlates { get; set; }
public decimal? WeeklyPlateCharge { get; set; }
public bool EnableEscrowDeductionOnPayroll { get; set; }
public decimal WeeklyEscrowDeduction { get; set; }
public bool ShowPersonalConveyance { get; set; } = false;
public bool ShowYardMoves { get; set; } = false;
public string StartTimeOfDay { get; set; } = "00:00:00.000";
}
and many view model classes, each of them can be mapped to this domain class:
public class DriverPersonalInfoVM
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public System.DateTime? DOB { get; set; }
public string SSN { get; set; }
public string PhoneNo { get; set; }
...
public class DriverEmploymentVM
{
public int Id { get; set; }
public System.DateTime? HireDate { get; set; }
public System.DateTime? TermDate { get; set; }
public bool DrugTest { get; set; }
public bool OnlineAccess { get; set; }
public bool ViewedSchedule { get; set; }
map rules:
CreateMap<Domain.POCO.Driver.DriverDomain, DriverPersonalInfoVM>();
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEmploymentVM>();
CreateMap<Domain.POCO.Driver.DriverDomain, DriverPayrollVM>();
CreateMap<Domain.POCO.Driver.DriverDomain, DriverCompensationVM>();
CreateMap<Domain.POCO.Driver.DriverDomain, DriverFuelTollsVM>();
CreateMap<Domain.POCO.Driver.DriverDomain, DriverAvatarVM>();
it works fine.
But now I have the following view model class:
public class DriverEditVM
{
public DriverEditVM(int id)
{
Id = id;
PersonalInfo = new DriverPersonalInfoVM { Id = id };
Employment = new DriverEmploymentVM { Id = id };
Payroll = new DriverPayrollVM { Id = id };
Compensation = new DriverCompensationVM { Id = id };
FuelTolls = new DriverFuelTollsVM { Id = id };
Avatar = new DriverAvatarVM { Id = id };
}
public DriverPersonalInfoVM PersonalInfo { get; set; }
public DriverEmploymentVM Employment { get; set; }
public DriverPayrollVM Payroll { get; set; }
public DriverCompensationVM Compensation { get; set; }
public DriverFuelTollsVM FuelTolls { get; set; }
public DriverAvatarVM Avatar { get; set; }
}
and map rule:
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEditVM>();
but when I try to map domain object to DriverEditVM:
var driver = _driverService.GetDriver(id.Value);
DriverEditVM model = mapper.Map<DriverEditVM>(driver);
I have empty properties PersonalInfo, Employment etc. How to map it?
As you have created maps for all your other view models that form part of your DriverEditVM, you should be able to do this:
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEditVM>()
.ForAllMembers(opt => opt.MapFrom(src => src));
Edit
As some members are not being mapped, there are two approaches, explicitly ignore the un-mapped members or explicitly map the mapped members:
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEditVM>()
.ForMember(dest => dest.IgnoredProperty1, opt => opt.Ignore())
.ForMember(dest => dest.IgnoredProperty2, opt => opt.Ignore())
.ForAllOtherMembers(opt => opt.MapFrom(src => src));
Or
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEditVM>()
.ForMember(dest => dest.PersonalInfo, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.Employment, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.Payroll, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.Compensation, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.FuelTolls, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.Avatar, opt => opt.MapFrom(src => src))
.ForAllOtherMembers(opt => opt.Ignore());
Solved this problem :
CreateMap<Domain.POCO.Driver.DriverDomain, DriverEditVM>()
.ForMember(p => p.PersonalInfo, p => p.MapFrom(src => src))
.ForMember(p => p.Avatar, p => p.MapFrom(src => src))
.ForMember(p => p.Compensation, p => p.MapFrom(src => src))
.ForMember(p => p.Employment, p => p.MapFrom(src => src))
.ForMember(p => p.FuelTolls, p => p.MapFrom(src => src))
.ForMember(p => p.Payroll, p => p.MapFrom(src => src))
;

Automapper better way of mapping property with custom logic

I have a simple class called Supplier which has contacts and contact can have addresses. right now we are using only 1 contact and 1 address (line1 to line4) in that contact. In future we might use multiple contacts having multiple addresses. below is my class
public class SupplierDto
{
public string Name { get; set; }
public string Alias { get; set; }
public int? SupplierTypeId { get; set; }
public int? WebclicsManufacturerId { get; set; }
public string SAPCode { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string Line4 { get; set; }
public int CountryId { get; set; }
public string PostalCode { get; set; }
public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactTelephone { get; set; }
public string ContactJobTitle { get; set; }
}
My Supplier class is a bit complex, so I am trying to map from Supplier to DTO and below is my mapping.
CreateMap<Supplier, SupplierDto>()
.ForMember(dest => dest.Line1, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.Line1))
.ForMember(dest => dest.Line2, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.Line2))
.ForMember(dest => dest.Line3, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.Line3))
.ForMember(dest => dest.Line4, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.Line4))
.ForMember(dest => dest.CountryId, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.CountryId))
.ForMember(dest => dest.PostalCode, options => options.MapFrom(source => source.SupplierContacts.First().Contact.EntityAddresses.First().Address.PostalCode))
.ForMember(dest => dest.ContactName, options => options.MapFrom(source => source.SupplierContacts.First().Contact.Name))
.ForMember(dest => dest.ContactEmail, options => options.MapFrom(source => source.SupplierContacts.First().Contact.Email))
.ForMember(dest => dest.ContactTelephone, options => options.MapFrom(source => source.SupplierContacts.First().Contact.Telephone))
.ForMember(dest => dest.ContactJobTitle, options => options.MapFrom(source => source.SupplierContacts.First().Contact.JobTitle));
As you can see I have custom logic for each column. Now problem is if there is no contact/address, code breaks because I am using
First()
from LINQ, is there a better way to check if contacts exists then do mapping and if it has addresses then proceed with address mapping?
Just create an ContactDto and add it to the SupplierDto, then move all contact relevant properties to the ContactDto.
public class ContactDto
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string Line4 { get; set; }
public int CountryId { get; set; }
public string PostalCode { get; set; }
public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactTelephone { get; set; }
public string ContactJobTitle { get; set; }
}
public class SupplierDto
{
public string Name { get; set; }
public string Alias { get; set; }
public int? SupplierTypeId { get; set; }
public int? WebclicsManufacturerId { get; set; }
public string SAPCode { get; set; }
public ContactDto Contact { get; set; }
}
Then the mapping should look something like this
CreateMap<Supplier, SupplierDto>()
.ForMember(dest => dest.Contact, options => options.MapFrom(source => source.SupplierContacts.FirstOrDefault()));
If Contact is null then AutoMapper didn't try to map it. If you want in the Future support multiple Contacts on the Supplier, then just change the ContactDto Contact Property to a List<ContactDto> and remove the .FirstOrDefault() in the Mapping.

Categories

Resources