投影到可以为空的枚举时,AutoMapper会抛出异常

时间:2016-12-29 18:28:29

标签: c# linq exception automapper

在我的项目中,我有一个Linq可查询(实际上是一个EF6集合),我需要将其转换为数据传输对象的集合。我在整个项目中使用AutoMapper,特别是因为它能够进行类型投影,从而减少了Linq查询生成的SQL数量。

但是我的一个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作为可以为空的枚举?

非常感谢。

2 个答案:

答案 0 :(得分:1)

您可以手动将MethodName映射到Method,除非我在您的问题中遗漏了某些内容。

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));
    });
}

答案 1 :(得分:1)

最新的AutoMapper v5.2.0也是如此。

在查看源代码之后,我认为这是ExpressionBuilder类中的一个错误,因为由于某些未知原因,NullableExpressionBinder的优先级高于CustomProjectionExpressionBinder(和其他),所以基本上,当您将非可空类型映射为可空时,将忽略所有自定义映射。

我建议你在他们的存储库上报告。在那之前,我可以建议你以下的解决方法(hack)。在项目中包含以下自定义类:

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();

问题应该解决。