首页 > 解决方案 > 如何使用 Automapper 展平实体层次结构列表?

问题描述

我想使用 automapper 展平从 Entity Framework Core 返回的实体层次结构列表。

这是我的实体:

public class Employee {
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public double? PayRateRegular { get; set; }
    public double? PayRateLoadedRegular { get; set; }
    public double? GMOutput { get; set; }
    public string EmployeeType { get; set; }

    //List of CommissionDetails where this employee is the consultant
    public IEnumerable<CommissionDetail> CommissionDetailConsultants { get; set; } = new List<CommissionDetail>();
}

public class Project {

    public int Id { get; set; }
    public string Description { get; set; }
    public double? BillRateRegular { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }

    public Customer Customer { get; set; }
    public int CustomerId { get; set; }

}

public class Customer {

    public int Id { get; set; }
    public string Name { get; set; }

}   

public class CommissionDetail {

    public string SaleType { get; set; }
    public double CommissionPercent { get; set; }
    public bool? IsReported { get; set; }
    public int? Level { get; set; }
    public string BasedOn { get; set; }

    public Project Project { get; set; }
    public int ProjectId { get; set; }

    public Employee SalesPerson { get; set; }
    public int SalesPersonEmployeeId { get; set; }

    public Employee Consultant { get; set; }
    public int ConsultantEmployeeId { get; set; }

}

这是我的 DTO:

public class ConsultantGridViewModel
{
    public string ConsultantName { get; set; }
    public string CustomerName { get; set; }
    public string SalesPersonName { get; set; }
    public string ProjectDescription { get; set; }
    public double? PayRate { get; set; }
    public double? LoadedRated { get; set; }
    public double? BillRate { get; set; }
    public double? GM { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public double CommissionPercent { get; set; }
    public int? CommissionLevel { get; set; }

}

这是我给 EF 的电话:

        return await _dbContext.Employee
            .AsNoTracking()
            .Include(e => e.CommissionDetailConsultants)
                .ThenInclude(cd => cd.SalesPerson)
            .Include(e => e.CommissionDetailConsultants)
                .ThenInclude(cd => cd.Project)
                    .ThenInclude(p => p.Customer)
            .Where(e => e.EmployeeType == "Contractor")
            .ToListAsync();

我目前正在使用 SelectMany 将其展平,如下所示:

var consultants = employees.SelectMany(e =>
    e.CommissionDetailConsultants,
    (emp, com) => new ConsultantGridViewModel {
        ConsultantName = emp.Name,
        PayRate = emp.PayRateRegular,
        LoadedRated = emp.PayRateLoadedRegular,
        GM = emp.GMOutput,
        BillRate = com.Project.BillRateRegular,
        ProjectDescription = com.Project.Description,
        ProjectStartDate = com.Project.StartDate,
        ProjectEndDate = com.Project.EndDate,
        CustomerName = com.Project.Customer.Name,
        SalesPersonName = com.SalesPerson.Name,
        CommissionPercent = com.CommissionPercent,
        CommissionLevel = com.Level
    });    

我想改用自动映射器。我已经将 automapper 用于所有其他 DTO 映射,但我不知道如何使用它来展平这样的嵌套对象。

标签: c#entity-frameworkentity-framework-coreautomapper

解决方案


让我们使用导航属性用SelectMany+重写您当前拥有的内容:SelectConsultant

var consultants = employees
    .SelectMany(e => e.CommissionDetailConsultants)
    .Select(com => new ConsultantGridViewModel
    {
        ConsultantName = com.Consultant.Name,
        PayRate = com.Consultant.PayRateRegular,
        LoadedRated = com.Consultant.PayRateLoadedRegular,
        GM = com.Consultant.GMOutput,
        BillRate = com.Project.BillRateRegular,
        ProjectDescription = com.Project.Description,
        ProjectStartDate = com.Project.StartDate,
        ProjectEndDate = com.Project.EndDate,
        CustomerName = com.Project.Customer.Name,
        SalesPersonName = com.SalesPerson.Name,
        CommissionPercent = com.CommissionPercent,
        CommissionLevel = com.Level
    });

现在可以看到CommissionDetail包含所有必要的数据,所以虽然你无法避免,但你可以通过创建一个映射 from to并使用类似这样的东西SelectMany来替换:SelectCommissionDetailConsultantGridViewModel

var consultants = Mapper.Map<List<ConsultantGridViewModel>>(
    employees.SelectMany(e => e.CommissionDetailConsultants));

甚至更好的是,直接投射到 DTO:

var consultants = await _dbContext.Employee
    .Where(e => e.EmployeeType == "Contractor")
    .SelectMany(e => e.CommissionDetailConsultants)
    .ProjectTo<ConsultantGridViewModel>()
    .ToListAsync();

现在映射。

AutoMapper 将自动映射成员,例如CommisionPercent. 此外,展平功能将自动处理Project.EndDate-> ProjectEndDateConsultant.Name->ConsultantName等 映射。

因此,与 AutoMapper 一样,您应该手动指定不属于先前类别的属性的映射。在这种情况下,最小配置是这样的:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<CommissionDetail, ConsultantGridViewModel>()
        .ForMember(dst => dst.PayRate, opt => opt.MapFrom(src => src.Consultant.PayRateRegular))
        .ForMember(dst => dst.LoadedRated, opt => opt.MapFrom(src => src.Consultant.PayRateLoadedRegular))
        .ForMember(dst => dst.GM, opt => opt.MapFrom(src => src.Consultant.GMOutput))
        .ForMember(dst => dst.BillRate, opt => opt.MapFrom(src => src.Project.BillRateRegular))
        .ForMember(dst => dst.CustomerName, opt => opt.MapFrom(src => src.Project.Customer.Name))
        .ForMember(dst => dst.CommissionLevel, opt => opt.MapFrom(src => src.Level));
});

PS您甚至可以SelectMany通过直接基于CommissionDetail实体的查询来避免,例如

var consultants = await _dbContext.Set<CommissionDetail>()
    .Where(c => c.Consultant.EmployeeType == "Contractor")
    .ProjectTo<ConsultantGridViewModel>()
    .ToListAsync();

请注意,当您进行直接投影时,不需要AsNoTrackingor Include/ ThenInclude


推荐阅读