Home > Blockchain >  Unflattening data list into a nested list using AutoMapper
Unflattening data list into a nested list using AutoMapper

Time:11-21

I have a flatten data list (counties and cities) and I want to map into a unflatten (counties with cities) nested property list (State.County). Please check the following .NET 6.0 console application code:

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 goof enough for the example.
        bool result = mappedData.Counties == unflattenData;
    }
}

I'm receiving the following exception:

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

What I'm doing wrong? What mappers should I create to transform the unflatten list into the flatten nested property: "Counties"?

CodePudding user response:

You may look for the Custom Value Resolver to group data for County.

public class StateCountiesResolver : IValueResolver<List<CountyAndCity>, State, List<County>>
{
    public List<County> Resolve(List<CountyAndCity> src, State dest, List<County> destMember, ResolutionContext ctx)
    {
        destMember = src.GroupBy(x => x.CountyId)
            .Select(x => new County
            {
                Id = x.Key,
                Name = x.FirstOrDefault()?.CountyName,
                Cities = ctx.Mapper.Map<List<City>>(x.ToList())
            })
            .ToList();
        
        return destMember;
    }
}
public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        CreateMap<CountyAndCity, City>()
            .ForMember(destination => destination.Id, options => options.MapFrom(source => source.CityId))
            .ForMember(destination => destination.Name, options => options.MapFrom(source => source.CityName))
            .ForMember(destination => destination.Population, options => options.MapFrom(source => source.CityPopulation));

        CreateMap<List<CountyAndCity>, State>()
            .ForMember(destination => destination.Counties, options => options.MapFrom<StateCountiesResolver>());
    }
}

Demo @ .NET Fiddle


While with the below statement:

bool result = mappedData.Counties == unflattenData;

you will still get false when comparing both lists (even the contents of both lists are the same) as the references are used to compare instead of the content of both lists:

Instead, you need to override the Equals() and GetHashCode() methods.

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Population { get; set; }
    
    
    public override bool Equals (Object obj)
    {
        if (obj is not City)
            return false;
        
        City b = (City)obj;
        
        return Id == b.Id
            && Name == b.Name
            && Population == b.Population;
    }
    
    
    public override int GetHashCode()
    {
        return this.Id.GetHashCode() ^ this.Name.GetHashCode() ^ this.Population.GetHashCode(); 
    }
}

public class County
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<City> Cities { get; set; }
    
    
    public override bool Equals (Object obj)
    {
        if (obj is not County)
            return false;
        
        County b = (County)obj;
        
        return Id == b.Id
            && Name == b.Name
            && Enumerable.SequenceEqual(Cities, b.Cities);
    }
    
    
    public override int GetHashCode()
    {
        return this.Id.GetHashCode() ^ this.Name.GetHashCode(); 
    }
}

And use Enumerable.SequenceEqual() method to compare the contents of the lists.

bool result = Enumerable.SequenceEqual(mappedData.Counties, unflattenData);
  • Related