Reputation: 2301
Here I am stucked with the conversion of dictionary
to Icollection
in EF Core. I have Dictionary
in FlatEmployee
class in which I am storing list of key, value pair in database. I have declared like this:
public class FlatEmployee
{
public int EmployeeId { get; set; }
public Dictionary<string, long> PayAndAllowances { get; set; }
}
//====================Configuration
public void Configure(EntityTypeBuilder<FlatEmployee> builder)
{
builder.HasKey(f => new { f.EmployeeId });
builder.Property(sb => sb.PayAndAllowances)
.HasConversion(
pa => JsonConvert.SerializeObject(pa),
pa => JsonConvert.DeserializeObject<Dictionary<string, long>>(pa));
}
This works absolutely fine when I am seeding or inserting. But the problem I am facing when I am trying to get the FlatEmployee class. This is because I want to get the dictionary in Collection. For that purpose I have declared another class like this:
public class LastPayCertificateViewModel: IHaveCustomMapping
{
public int EmployeeCode { get; set; }
public EmployeeEarningDTO PayAndAllowances { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<FlatEmployee, LastPayCertificateViewModel>()
.ForMember(dto => dto.EmployeeCode , opt => opt.MapFrom(entity => entity.EmployeeId ));
}
}
public class EmployeeEarningDTO : IHaveCustomMapping
{
public ICollection<BaseEmployeeDictionary> PayAndAllowances { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>()
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity.PayAndAllowances));
}
}
public class BaseEmployeeDictionary
{
public string Name { get; set; }
public long Amount { get; set; }
}
When I am trying to use above classes for getting the data like this:
public class LastPayCertificateQuery : IRequest<LastPayCertificateViewModel>
{
public string EmployeeCode { get; set; }
}
public async Task<LastPayCertificateViewModel> Handle(LastPayCertificateQuery request, CancellationToken cancellationToken)
{
var predicate = PredicateBuilder.False<FlatEmployee>();
predicate = predicate.Or(emp => emp.EmployeeId == request.EmployeeCode);
var employee = await _context.FlatEmployee
.Where(predicate)
.FirstOrDefaultAsync(cancellationToken);
if (employee == null)
return null;
var empVM = _mapper.Map<LastPayCertificateViewModel>(employee);
}
Then I am getting null in PayAndAllowances in empVM
. This is what my problem is. Where is my problem? I thought it was because that Dictionary has key value pair and which is not been able to convert to BaseEmployeeDictionary
. I have tried this way as well to add List Item to PayAndAllowances in empVM
foreach (KeyValuePair<string, long> data in employee.DeductionsByAdjustment)
{
BaseEmployeeDictionary listItem = new BaseEmployeeDictionary
{
Name = data.Key,
Amount = data.Value
};
empVM.EarningDetails.PayAndAllowances.Add(listItem);
return empVM;
}
Which of course wont work because the empVM.EarningDetails.PayAndAllowances
is null and throws NullReferenceException. My queries is how to map between the Dictionary to ICollection while Creating Map in EmployeeEarningDTO
. OR It would be really appretiated for your valuable suggestion and solution please.
Upvotes: 1
Views: 1931
Reputation: 205829
It turns out to be AutoMapper mapping issue.
First, EmployeeEarningDTO
inside LastPayCertificateViewModel
creates additional level compared to FlatEmployee
:
LastPayCertificateViewModel.PayPayAndAllowances.PayAndAllowances
vs
FlatEmployee.PayAndAllowances
AutoMapper maps by default properties with the same name. So inside FlatEmployee
to LastPayCertificateViewModel
map it would try to map Dictionary<string, long> PayAndAllowances
to EmployeeEarningDTO PayAndAllowances
. But there is no mapping from Dictionary<string, long>
to EmployeeEarningDTO
. Instead, there is a mapping from FlatEmployee
to EmployeeEarningDTO
, so you have to tell AM to use it:
configuration.CreateMap<FlatEmployee, LastPayCertificateViewModel>()
.ForMember(dto => dto.EmployeeCode, opt => opt.MapFrom(entity => entity.EmployeeId))
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity)); // <--
Second, mapping from FlatEmployee
to EmployeeEarningDTO
- AM will automatically try to map PayAndAllowances
properties, but there is no mapping from KeyValuePair<string, long>
to BaseEmployeeDictionary
. You could define such mapping
configuration.CreateMap<KeyValuePair<string, long>, BaseEmployeeDictionary>()
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dst => dst.Amount, opt => opt.MapFrom(src => src.Value));
which will allow you to use simply
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>();
however you probably won't do that because you don't want every KeyValuePair<string, long>
to be mapped to BaseEmployeeDictionary
, so you could do that mapping inplace:
configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>()
.ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity.PayAndAllowances
.Select(src => new BaseEmployeeDictionary { Name = src.Key, Amount = src.Value })));
Upvotes: 2