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
相关产品推荐
相关产品推荐

