Iswar
Iswar

Reputation: 2301

Convert Dictionary to ICollection in Entity Framework Core

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

Answers (1)

Ivan Stoev
Ivan Stoev

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

Related Questions