如何使用AutoMapper正确配置`int?`到`int`投影?

Nic*_*oad 6 c# automapper

我在使这个工作正常时遇到了一些麻烦.我有两节课:

public class TestClassA
{
    public int? NullableIntProperty { get; set; }
}

public class TestClassB
{
    public int NotNullableIntProperty { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后我设置以下映射:

cfg.CreateMap<TestClassA, TestClassB>()
    .ForMember(dest => dest.NotNullableIntProperty,
               opt => opt.MapFrom(src => src.NullableIntProperty));

cfg.CreateMap<TestClassA, TestClassA>()
    .ForMember(dest => dest.NullableIntProperty,
               opt => opt.MapFrom(src => src.NullableIntProperty));

cfg.CreateMap<TestClassB, TestClassA>()
    .ForMember(dest => dest.NullableIntProperty,
               opt => opt.MapFrom(src => src.NotNullableIntProperty));

cfg.CreateMap<TestClassB, TestClassB>()
    .ForMember(dest => dest.NotNullableIntProperty,
               opt => opt.MapFrom(src => src.NotNullableIntProperty));
Run Code Online (Sandbox Code Playgroud)

我现在已经设置了四个映射,并将测试以下场景:

int? => int
int => int?
int => int
int? => int?
Run Code Online (Sandbox Code Playgroud)

在测试类中,我然后使用这样的映射:

var testQueryableDest = testQueryableSrc.ProjectTo<...>(_mapper.ConfigurationProvider);
Run Code Online (Sandbox Code Playgroud)

我期望在这个阶段不能工作的唯一预测是TestClassA => TestClassB,因为我可以看到AutoMapper可能不知道如何处理int?值的情况null.果然,情况确实如此.所以我设置了这样的映射int? => int:

cfg.CreateMap<int?, int>()
    .ProjectUsing(src => src ?? default(int));
Run Code Online (Sandbox Code Playgroud)

事情变得奇怪了.只要我添加此映射,映射TestClassB => TestClassB就会失败甚至创建.它给出了以下错误消息:

类型'System.Int32'的表达式不能用于赋值类型'System.Nullable`1 [System.Int32]'

我发现这条消息非常奇怪,因为TestClassB根本没有int?它.那么这里发生了什么?我觉得我必须误解一下AutoMapper如何处理这些预测.我意识到各种代码可能很难拼凑起来所以这里是整个测试类供参考:

[TestClass]
public class BasicTests
{
    private readonly IMapper _mapper;

    public BasicTests()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<int?, int>()
                .ProjectUsing(src => src ?? default(int));

            cfg.CreateMap<TestClassA, TestClassB>()
                .ForMember(dest => dest.IntProperty, opt => opt.MapFrom(src => src.NullableIntProperty));

            cfg.CreateMap<TestClassA, TestClassA>()
                .ForMember(dest => dest.NullableIntProperty, opt => opt.MapFrom(src => src.NullableIntProperty));

            cfg.CreateMap<TestClassB, TestClassA>()
                .ForMember(dest => dest.NullableIntProperty, opt => opt.MapFrom(src => src.IntProperty));

            cfg.CreateMap<TestClassB, TestClassB>()
                .ForMember(dest => dest.IntProperty, opt => opt.MapFrom(src => src.IntProperty));
        });

        _mapper = new Mapper(config);
    }

    [TestMethod]
    public void CanMapNullableIntToInt()
    {
        var testQueryableSource = new List<TestClassA>
        {
            new TestClassA
            {
                NullableIntProperty = null
            }
        }.AsQueryable();

        var testQueryableDestination = testQueryableSource.ProjectTo<TestClassB>(_mapper.ConfigurationProvider);
    }

    [TestMethod]
    public void CanMapNullableIntToNullableInt()
    {
        var testQueryableSource = new List<TestClassA>
        {
            new TestClassA
            {
                NullableIntProperty = null
            }
        }.AsQueryable();

        var testQueryableDestination = testQueryableSource.ProjectTo<TestClassA>(_mapper.ConfigurationProvider);
    }

    [TestMethod]
    public void CanMapIntToNullableInt()
    {
        var testQueryableSource = new List<TestClassB>
        {
            new TestClassB
            {
                IntProperty = 0
            }
        }.AsQueryable();

        var testQueryableDestination = testQueryableSource.ProjectTo<TestClassA>(_mapper.ConfigurationProvider);
    }

    [TestMethod]
    public void CanMapIntToInt()
    {
        var testQueryableSource = new List<TestClassB>
        {
            new TestClassB
            {
                IntProperty = 0
            }
        }.AsQueryable();

        var testQueryableDestination = testQueryableSource.ProjectTo<TestClassB>(_mapper.ConfigurationProvider);
    }
}
Run Code Online (Sandbox Code Playgroud)

pok*_*oke 3

我\xe2\x80\x99ve发现重现这种情况的最短方法如下:

\n\n
var config = new MapperConfiguration(cfg =>\n{\n    cfg.CreateMap<int?, int>().ProjectUsing(x => x ?? default(int));\n    cfg.CreateMap<TestClassA, TestClassA>()\n        .ForMember(a => a.NullableIntPropety, o => o.MapFrom(a => a.NullableIntProperty));\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我看来,AutoMapper 正在尝试int? => int在这里使用映射器,尽管这里将使用更明显的基于身份的映射。

\n\n

由于 everyint也是有效的int?,因此 AutoMapper尝试在此处使用int? => int映射器并将结果分配给该int?成员。但似乎在解决该分配时,在幕后某些东西无法正确工作,因此出现了异常。

\n\n

解决这个问题的方法似乎是添加另一个映射,即身份映射int? => int?

\n\n
cfg.CreateMap<int?, int?>().ProjectUsing(x => x);\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,将使用此映射,并且不会发生异常(并且该映射也可以在所有示例中正常工作\xe2\x80\x94)。

\n\n
\n\n

这个问题似乎存在于当前的 AutoMapper 5.1.x 版本上(当前是 5.1.1)。好消息是,它已经得到修复。如果您尝试myget feed中的当前 5.2 alpha ,那么代码可以正常工作,没有任何问题。

\n\n

自 5.1.1 版本以来,代码库已经做出了相当多的贡献,对可空映射进行了多个修复(例如thisthis拉取请求)。我认为其中一项更改已经解决了这个问题。

\n\n

最有可能的是 Pull request #1672,它只是为了删除不需要的代码,但显然也修复了问题 1664,该问题是关于 AutoMapper 显然优先考虑可空源映射而不是不可空源,即使正在映射不可空源。这听起来很像你所经历过的这个问题。

\n\n

因此,目前,您可以添加上述解决方法以将类型映射到自身或使​​用 alpha 版本,同时我们等待 5.2 发布。

\n