Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automapper Throwing Odd Mapping Error

Automapper is throwing this error at me: Missing map from String to String. Create using Mapper.CreateMap<String, String>.

The map is used in two places. in one place it works fine, in another it fails.

The mapping profile is this:

public class AdminUserProfileProfile: Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<AdminUser, AdminUserProfile>()
              .ForMember(vm => vm.Id, opt => opt.MapFrom(m => m.Id))
              .ForMember(vm => vm.Name, opt => opt.MapFrom(m => m.Name))
              .ForMember(vm => vm.Email, opt => opt.MapFrom(m => m.Email))
              .ForMember(vm => vm.Roles, opt => opt.MapFrom(m => m.Roles.Select(r => r.Name)))
              .IgnoreAllNonExisting();
    }
}

The only difference in use case is that the mapping which behaves as expected uses Mapper.Map<AdminUserProfile>(entity) and the one that fails is used via a `Project().To' call.

I would like to use the projection capabilities of Project().To<>, what do I need to do to get this to work?

like image 248
ilivewithian Avatar asked Mar 21 '23 14:03

ilivewithian


1 Answers

Just spent some time in the AutoMapper source figuring this out. The issue comes from a bug in type handling, but there's not an easy fix in the automapper source.

TL;DR: If you make AdminUserProfile.Roles be of type IEnumerable<string>, I think it will work.

.ForMember(vm => vm.Roles, opt => opt.MapFrom(m => m.Roles.Select(r => r.Name)))

I'll bet a dollar AdminUserProfile.Roles is something like string[], ICollection<string>, or List<string>.

AutoMapper is trying to assemble a linq query that looks kinda like this:

admins.Select(a => new AdminUserProfile{
  Id = a.Id,
  Name = a.Name,
  Roles = a.Roles.Select(r => r.Name)
})

But that isn't legal, because the types on Roles = a.Roles.Select(r => r.Name) don't match. In my case, my Roles property was a ICollection<string>, but a.Roles.Select is returning an IEnumerable<string>, and you can't assign those directly.

I patched the automapper source to fix the Missing map from String to String. error, and once past that it will try to add a ToList in there, generating something like:

admins.Select(a => new AdminUserProfile{
  Id = a.Id,
  Name = a.Name,
  Roles = a.Roles.Select(r => r.Name).ToList()
})

This is neat, but if you use it with Linq-to-entities you'll get a runtime LINQ to Entities does not recognize the method ToList error.

I think the "real" fix would be to make significant changes to automapper and have it generate something like:

admins.Select(a => new {
  a.Id,
  a.Name,
  Roles = a.Roles.Select(r => r.Name)
})
.AsEnumerable() // run the linq-to-entities query
.Select(a => new AdminUserProfile{
  Id = a.Id,
  Name = a.Name,
  Roles = a.Roles.ToList()
})

The code is pretty dense, so I'm unlikely to make this fix.

I'd recommend just change AdminUserProfile.Roles to IEnumerable<string>.

like image 90
Ryan Davis Avatar answered May 10 '23 02:05

Ryan Davis