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

使用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
*/

错误分析

  1. CountyAndCity到City的映射字段错误:把CountyId映射到City.Id,应该使用CityId;同时City.Name应该映射CityName而非CountyName,这会导致城市信息完全错误。
  2. State映射未处理分组逻辑:直接将整个CountyAndCity集合传给State.Counties,AutoMapper无法自动将单个CountyAndCity转换为County,必须先按CountyId分组,再将每组映射为对应的County。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 06:20:27