在实体框架核心中将字典转换为ICollection

时间:2019-04-23 07:02:10

标签: c# ef-code-first entity-framework-core asp.net-core-webapi asp.net-core-2.1

在这里,我坚持将EF Core中的dictionary转换为Icollection。我在Dictionary类中有FlatEmployee,在其中将键值对列表存储在数据库中。我已经这样声明了:

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));
}

当我播种或插入种子时,这绝对可以正常工作。但是,当我尝试获取FlatEmployee类时遇到的问题。这是因为我想在Collection中获取字典。为此,我声明了另一个这样的类:

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; }
}

当我试图使用上述类来获取数据时,

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);
}

然后我在PayAndAllowances in empVM中变成空。这就是我的问题。我的问题在哪里?我以为是因为Dictionary具有键值对,并且无法将其转换为BaseEmployeeDictionary。我也尝试过这种方式将列表项添加到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;
  }

这当然是行不通的,因为empVM.EarningDetails.PayAndAllowances为null并抛出NullReferenceException。我的查询是如何在EmployeeEarningDTO中创建Map时在Dictionary与ICollection之间进行映射。否则,非常感谢您的宝贵建议和解决方案。

1 个答案:

答案 0 :(得分:2)

原来是AutoMapper映射问题。

首先,与EmployeeEarningDTO相比,LastPayCertificateViewModel内部的FlatEmployee创建了附加级别:

LastPayCertificateViewModel.PayPayAndAllowances.PayAndAllowances

vs

FlatEmployee.PayAndAllowances

默认情况下,AutoMapper映射具有相同名称的属性。因此,在FlatEmployeeLastPayCertificateViewModel映射中,它将尝试将Dictionary<string, long> PayAndAllowances映射到EmployeeEarningDTO PayAndAllowances。但是从Dictionary<string, long>EmployeeEarningDTO没有映射。而是从FlatEmployeeEmployeeEarningDTO的映射,因此您必须告诉AM使用它:

configuration.CreateMap<FlatEmployee, LastPayCertificateViewModel>()
    .ForMember(dto => dto.EmployeeCode, opt => opt.MapFrom(entity => entity.EmployeeId))
    .ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity)); // <--

第二,从FlatEmployeeEmployeeEarningDTO的映射-AM将自动尝试映射PayAndAllowances的属性,但是没有从KeyValuePair<string, long>BaseEmployeeDictionary的映射。您可以定义这样的映射

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));

这将使您可以简单地使用

configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>();

但是您可能不会这样做,因为您不希望将每个 KeyValuePair<string, long>映射到BaseEmployeeDictionary,因此可以就地进行映射:

configuration.CreateMap<FlatEmployee, EmployeeEarningDTO>()
     .ForMember(dto => dto.PayAndAllowances, opt => opt.MapFrom(entity => entity.PayAndAllowances
        .Select(src => new BaseEmployeeDictionary { Name = src.Key, Amount = src.Value })));