2016-12-29 2 views
1

В моем проекте у меня есть запрос Linq (на самом деле коллекция EF6), который мне нужно преобразовать в коллекцию объектов передачи данных. Я использую AutoMapper на протяжении всего проекта, особенно за его способность выполнять проекцию типа, тем самым уменьшая количество SQL, сгенерированное в результате запроса Linq.AutoMapper выдает исключение при проецировании на nullable enum

Но у меня есть небольшая проблема с одним из моих классов DTO. Связанный столбец базы данных содержит строку с нулевым значением, которую я хочу сопоставить с нулевым перечислением. Но во время выполнения генерируется исключение

Нет Оператор принуждения не определен между типами «System.String» и 'System.Nullable`1 [AutomapperEnumTest.Method].

Вот полная тестовая программа, которая демонстрирует проблему (см .Net Fiddle)

using System; 
using System.Linq; 
using AutoMapper; 
using AutoMapper.QueryableExtensions; 

namespace AutomapperEnumTest 
{ 
    public class Program 
    { 
     public static void Main() 
     { 
      Configure(); 
      var src = new SourceType[] { new SourceType { Name = "Rob", MethodName = "AUTO" } }; 
      var results = src.AsQueryable() 
         .ProjectTo<DestType>(); 

      foreach(var item in results) 
      { 
       Console.WriteLine(String.Format("DestType Name={0} Method={1}", item.Name, item.Method)); 
      } 
      Console.WriteLine("Done"); 
     } 

     private static void Configure() 
     { 
      Mapper.Initialize(cfg => 
      { 
       cfg.CreateMap<string, Method?>().ProjectUsing(src => src == "MANUAL" ? Method.MANUAL : Method.AUTO); 
       cfg.CreateMap<SourceType, DestType>() 
        .ForMember(dest => dest.Method, opt => opt.MapFrom(src => src.MethodName)); 
      }); 
     } 
    } 

    public enum Method 
    { 
     MANUAL=0, 
     AUTO=1 
    } 

    public class DestType 
    { 
     public string Name {get; set; } 
     public Method? Method {get; set; } 
    } 

    public class SourceType 
    { 
     public string Name {get; set; } 
     public string MethodName {get; set; } 
    } 
} 

Теперь, если я изменить свойство от Method? к Method он работает отлично (см этой модификации в .Net Fiddle). Но я не хочу этого делать, поэтому мой вопрос заключается в том, как сообщить Linq/AutoMapper, что он должен использовать мой ProjectUsing для нумеруемого перечисления?

Большое спасибо.

ответ

1

То же самое происходит в последнем AutoMapper v5.2.0.

После просмотра исходного кода, я думаю, что это ошибка внутри ExpressionBuilder класса, потому что по какой-то неизвестной причине NullableExpressionBinder дается более высокий приоритет, чем CustomProjectionExpressionBinder (и другие), так что в основном, когда вы карту нон обнуляемого типа в NULLABLE, все пользовательские сопоставления игнорируются.

Я предлагаю вам сообщить об этом в своем репозитории. До тех пор я мог бы предложить вам следующее обходное решение (взломать). Включите следующий пользовательский класс в проекте:

using System.Linq.Expressions; 
using System.Reflection; 
using AutoMapper; 
using AutoMapper.QueryableExtensions; 
using AutoMapper.QueryableExtensions.Impl; 

class NullableExpressionBinderEx : IExpressionBinder 
{ 
    public static void Install() 
    { 
     var bindersField = typeof(ExpressionBuilder).GetField("Binders", BindingFlags.NonPublic | BindingFlags.Static); 
     var binders = (IExpressionBinder[])bindersField.GetValue(null); 
     binders[0] = new NullableExpressionBinderEx(); 
    } 
    IExpressionBinder baseBinder = new NullableExpressionBinder(); 
    private NullableExpressionBinderEx() { } 
    public bool IsMatch(PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionResolutionResult result) 
    { 
     if (propertyTypeMap != null && propertyTypeMap.CustomProjection != null) 
      return false; 
     return baseBinder.IsMatch(propertyMap, propertyTypeMap, result); 
    } 
    public MemberAssignment Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount) 
    { 
     return baseBinder.Build(configuration, propertyMap, propertyTypeMap, request, result, typePairCount); 
    } 
} 

затем добавить следующую строку в ваш метод Configure:

NullableExpressionBinderEx.Install(); 

и этот вопрос должен быть решен.

+1

Спасибо за предоставление такого полного ответа. Я протестировал его, и это решит проблему. – Rob

1

Вы можете сопоставить MethodName с методом вручную, если только я не упустил что-то в вашем вопросе.

private static void Configure() 
{ 
    Mapper.Initialize(cfg => 
    { 
     cfg.CreateMap<SourceType, DestType>() 
      .ForMember(dest => dest.Method, opt => opt.MapFrom(src => 
       src.MethodName == "MANUAL" ? Method.MANUAL : Method.AUTO)); 
    }); 
} 
+0

Спасибо @Win, ваше решение действительно работает в этом простом примере. Я не думал попробовать это, потому что в реальном приложении перечисление используется в нескольких местах, поэтому я бы предпочел, чтобы тип имел свою собственную конфигурацию сопоставления (например, конфигурацию «ProjectUsing»). Знаете ли вы, возможно ли это, а если нет, что мешает? – Rob

+0

в моем втором примере .Net Fiddle вы увидите, что существует глобальное отображение из 'String' в перечисление' Method'. Это работает отлично, но тот же метод, похоже, не работает для 'System.Nullable '. – Rob

+0

Это что-то не так в *** ProjectTo *** метод расширения 'src.AsQueryable(). ProjectTo ()'. Ваш код работает с 'var results = src.Select (Mapper.Map );' – Win

Смежные вопросы