使用AutoMapper将扁平化数据转嵌套State结构报错求助
扁平化County-City数据转嵌套State结构的AutoMapper配置问题
问题背景
有一份包含区县(County)与城市(City)的扁平化数据列表,需要通过AutoMapper将其转换为带有嵌套属性的State结构(State.County,每个County包含对应的Cities集合)。当前.NET 6控制台应用代码如下:
using AutoMapper; public class CountyAndCity { public int CountyId { get; set; } public string CountyName { get; set; } public int CityId { get; set; } public string CityName { get; set; } public int CityPopulation { get; set; } } public class City { public int Id { get; set; } public string Name { get; set; } public int Population { get; set; } } public class County { public int Id { get; set; } public string Name { get; set; } public IEnumerable<City> Cities { get; set; } } public class State { public List<County> Counties { get; set; } } public class AutoMapperProfile : Profile { public AutoMapperProfile() { CreateMap<CountyAndCity, City>() .ForMember(destination => destination.Id, options => options.MapFrom(source => source.CountyId)) .ForMember(destination => destination.Name, options => options.MapFrom(source => source.CountyName)) .ForMember(destination => destination.Population, options => options.MapFrom(source => source.CityPopulation)); CreateMap<IEnumerable<CountyAndCity>, County>() .ForMember(destination => destination.Id, options => options.MapFrom(source => source.FirstOrDefault().CountyId)) .ForMember(destination => destination.Name, options => options.MapFrom(source => source.FirstOrDefault().CountyName)) .ForMember(destination => destination.Cities, options => options.MapFrom(source => source)); CreateMap<IEnumerable<CountyAndCity>, State>() .ForMember(destination => destination.Counties, options => options.MapFrom(source => source)); } } class HelloWorld { static void Main() { var flattenData = new List<CountyAndCity> { new CountyAndCity { CountyId = 1, CountyName = "Albany", CityId = 10, CityName = "Cohoes", CityPopulation = 1000 }, new CountyAndCity { CountyId = 1, CountyName = "Albany", CityId = 20, CityName = "Watervliet", CityPopulation = 1000 }, new CountyAndCity { CountyId = 2, CountyName = "Bronx", CityId = 30, CityName = "Wakefield", CityPopulation = 2000 }, new CountyAndCity { CountyId = 2, CountyName = "Bronx", CityId = 40, CityName = "Eastchester", CityPopulation = 2000 }, }; var unflattenData = new List<County> { new County { Id = 1, Name = "Albany", Cities = new List<City> { new City { Id = 10, Name = "Cohoes", Population = 1000 }, new City { Id = 20, Name = "Watervliet", Population = 1000 }, }}, new County { Id = 2, Name = "Bronx", Cities = new List<City> { new City { Id = 30, Name = "Wakefield", Population = 2000 }, new City { Id = 40, Name = "Eastchester", Population = 2000 }, }}, }; var mapperConfiguration = new MapperConfiguration(configuration => configuration.AddProfile(new AutoMapperProfile())); var mapper = mapperConfiguration.CreateMapper(); State mappedData = mapper.Map<State>(flattenData); //Should be TRUE after some kind of custom comparison but it is good enough for the example. bool result = mappedData.Counties == unflattenData; } }
运行时抛出异常:
/* Error mapping types. Mapping types: IEnumerable`1 -> State System.Collections.Generic.IEnumerable`1[[CountyAndCity, ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> State Type Map configuration: IEnumerable`1 -> State System.Collections.Generic.IEnumerable`1[[CountyAndCity, ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> State Destination Member: Counties */
错误分析
CountyAndCity到City的映射字段错误:把CountyId映射到City.Id,应该使用CityId;同时City.Name应该映射CityName而非CountyName,这会导致城市信息完全错误。State映射未处理分组逻辑:直接将整个CountyAndCity集合传给State.Counties,AutoMapper无法自动将单个CountyAndCity转换为County,必须先按CountyId分组,再将每组映射为对应的County。County映射存在空引用风险:使用FirstOrDefault()获取分组后的County信息,如果分组为空会引发空引用异常,且逻辑上分组后的集合必然包含相同CountyId的元素,可以直接取第一个元素而无需FirstOrDefault()。
修正方案
1. 修正AutoMapper配置
调整映射逻辑,先分组再转换:
public class AutoMapperProfile : Profile { public AutoMapperProfile() { // 正确映射CountyAndCity到City CreateMap<CountyAndCity, City>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.CityId)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.CityName)) .ForMember(dest => dest.Population, opt => opt.MapFrom(src => src.CityPopulation)); // 将分组后的CountyAndCity集合映射为County CreateMap<IGrouping<int, CountyAndCity>, County>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Key)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.First().CountyName)) .ForMember(dest => dest.Cities, opt => opt.MapFrom(src => src)); // 将整个集合分组后映射为State的Counties CreateMap<IEnumerable<CountyAndCity>, State>() .ForMember(dest => dest.Counties, opt => opt.MapFrom(src => src.GroupBy(c => c.CountyId).OrderBy(g => g.Key))); } }
2. 修正结果比较逻辑
原代码中mappedData.Counties == unflattenData是引用比较,永远为false,需要改为结构比较,比如手动验证:
// 示例:手动验证核心数据是否一致 bool result = true; if (mappedData.Counties.Count != unflattenData.Count) result = false; else { for (int i = 0; i < mappedData.Counties.Count; i++) { var mappedCounty = mappedData.Counties[i]; var targetCounty = unflattenData[i]; if (mappedCounty.Id != targetCounty.Id || mappedCounty.Name != targetCounty.Name) { result = false; break; } if (mappedCounty.Cities.Count() != targetCounty.Cities.Count()) { result = false; break; } // 验证城市信息 var mappedCities = mappedCounty.Cities.ToList(); var targetCities = targetCounty.Cities.ToList(); for (int j = 0; j < mappedCities.Count; j++) { if (mappedCities[j].Id != targetCities[j].Id || mappedCities[j].Name != targetCities[j].Name || mappedCities[j].Population != targetCities[j].Population) { result = false; break; } } } } // 此时result应为true
说明
- 使用
IGrouping<int, CountyAndCity>作为County的源类型,直接利用分组后的Key作为CountyId,避免空引用问题。 - 映射
State时先对原始数据按CountyId分组,再将每个分组转换为County,完美实现扁平化数据到嵌套结构的转换。
内容的提问来源于stack exchange,提问作者Ricardo Sotolongo
相关产品推荐
相关产品推荐

