You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WebAPI中如何用AutoMapper.Collection映射ID列表为实体列表?

问题描述

在C# WebAPI项目中使用Entity Framework Core,定义了以下简化后的实体与DTO:

Division实体

public class Division
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int DivisionID { get; set; }

    public virtual IEnumerable<CompetitorLevel> CompetitorLevels { get; set; }
}

CompetitorLevel实体

public class CompetitorLevel
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int CompetitorLevelID { get; set; }

    [Required]
    public string Name { get; set; }

    public virtual IEnumerable<Division> Divisions { get; set; }
}

DivisionDTO

public class DivisionDTO
{
    public int? id { get; set; }

    public List<string> competitorLevels { get; set; }
}

该DTO用于与前端交互,目前已通过AutoMapper完成普通属性映射以及Division到DivisionDTO的转换,但无法实现将DTO中的ID列表转换为对应实体列表的功能。尝试过AutoMapper.Collection的EqualityComparison(...)和MapFrom(...)组合均未成功,当前在服务类中手动构建并映射实体:

public void Update(DivisionDTO request)
{
    if (request.id == null)
        throw new ValidationException("The ID attribute is required.");

    Division division = _db.Divisions
        .Include(d => d.CompetitorLevels)
        .FirstOrDefault(d => d.DivisionID == request.id.Value);

    if (division == null)
        throw new KeyNotFoundException("A division with that ID was not found.");

    _mapper.Map(request, division);
    UpdateCompetitorLevels(request, division);

    _db.SaveChanges();
}

public void UpdateCompetitorLevels(DivisionDTO request, Division division)
{
    // Remove competitor levels from division if not present in DTO
    foreach (CompetitorLevel competitorLevel in division.CompetitorLevels.ToList())
    {
        if (!request.competitorLevels.Select(cl => int.Parse(cl)).Contains(competitorLevel.CompetitorLevelID))
        {
            (division.CompetitorLevels as List<CompetitorLevel>).Remove(competitorLevel);
        }
    }

    // Add competitor level to division if present in DTO
    foreach (int competitorLevelID in request.competitorLevels.Select(cl => int.Parse(cl)))
    {
        if (!division.CompetitorLevels.Any(cl => cl.CompetitorLevelID == competitorLevelID))
        {
            (division.CompetitorLevels as List<CompetitorLevel>).Add(_db.CompetitorLevels.Find(competitorLevelID));
        }
    }
}

希望找到更优雅的AutoMapper实现方式。

解决方案

1. 安装必要NuGet包

除AutoMapper外,需安装专门处理集合映射和EF Core关联更新的包:

Install-Package AutoMapper
Install-Package AutoMapper.Collection
Install-Package AutoMapper.Collection.EntityFrameworkCore

2. 配置AutoMapper映射

在映射配置类中完成ID到实体的转换、DTO到实体的集合映射,并设置匹配规则:

public class MappingProfile : Profile
{
    private readonly AppDbContext _dbContext;

    // 注入DbContext用于根据ID查询实体
    public MappingProfile(AppDbContext dbContext)
    {
        _dbContext = dbContext;

        // 配置字符串ID转CompetitorLevel:解析ID并从数据库获取实体
        CreateMap<string, CompetitorLevel>()
            .ConvertUsing(s => 
            {
                if (int.TryParse(s, out int id))
                {
                    return _dbContext.CompetitorLevels.Find(id);
                }
                return null;
            });

        // 配置DivisionDTO到Division的映射
        CreateMap<DivisionDTO, Division>()
            .ForMember(dest => dest.DivisionID, opt => opt.MapFrom(src => src.id))
            // 映射competitorLevels集合
            .ForMember(dest => dest.CompetitorLevels, opt => opt.MapFrom(src => src.competitorLevels))
            // 设置Division的匹配规则:通过ID判断实体唯一性
            .EqualityComparison((src, dest) => src.DivisionID == dest.DivisionID);

        // 设置CompetitorLevel的集合匹配规则:通过ID判断实体唯一性
        CreateMap<CompetitorLevel, CompetitorLevel>()
            .EqualityComparison((src, dest) => src.CompetitorLevelID == dest.CompetitorLevelID);
    }
}

3. 简化服务类代码

移除手动处理集合的方法,直接通过AutoMapper完成全量映射:

public void Update(DivisionDTO request)
{
    if (request.id == null)
        throw new ValidationException("The ID attribute is required.");

    Division division = _db.Divisions
        .Include(d => d.CompetitorLevels)
        .FirstOrDefault(d => d.DivisionID == request.id.Value);

    if (division == null)
        throw new KeyNotFoundException("A division with that ID was not found.");

    // AutoMapper自动处理集合的新增、移除操作
    _mapper.Map(request, division);

    _db.SaveChanges();
}

关键说明

  • AutoMapper.Collection会自动对比源集合(DTO ID转换后的实体)和目标集合(已加载的CompetitorLevels),根据匹配规则自动执行新增、移除操作。
  • 注入DbContext到映射配置中,确保转换出的实体是EF跟踪状态,避免重复实体问题。
  • 建议将Division.CompetitorLevels的类型改为List<CompetitorLevel>(而非IEnumerable<CompetitorLevel>),确保集合可修改;若保持原类型,需在查询时用.ToList()转换为可修改集合。

内容的提问来源于stack exchange,提问作者Alexandre St-Amant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 01:31:05