【问题标题】:How to correctly configure `int?` to `int` projections using AutoMapper?如何使用 AutoMapper 正确配置 `int?` 到 `int` 投影?
【发布时间】:2016-10-13 03:49:50
【问题描述】:

我无法让它正常工作。我有两个班级:

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

public class TestClassB
{
    public int NotNullableIntProperty { get; set; }
}

然后我设置了以下映射:

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

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

int? => int
int => int?
int => int
int? => int?

在测试类中,我使用如下映射:

var testQueryableDest = testQueryableSrc.ProjectTo<...>(_mapper.ConfigurationProvider);

我希望在这个阶段不会工作的唯一预测是TestClassA =&gt; TestClassB,因为我可以看到 AutoMapper 在值为null 的情况下可能不知道如何处理int?。果然,事实就是如此。所以我为int? =&gt; int 设置了一个映射,如下所示:

cfg.CreateMap<int?, int>()
    .ProjectUsing(src => src ?? default(int));

这就是事情变得奇怪的地方。添加此映射后,TestClassB =&gt; 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);
    }
}

【问题讨论】:

    标签: c# automapper


    【解决方案1】:

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

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

    在我看来 AutoMapper 正在尝试在此处使用 int? =&gt; int 映射器,尽管此处将使用更明显的基于身份的映射。

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

    似乎解决这个问题的是添加另一个映射,int? =&gt; int? 的标识映射:

    cfg.CreateMap<int?, int?>().ProjectUsing(x => x);
    

    然后,将使用此映射,并且没有发生异常(并且该映射也可以正常工作 - 对于您的所有示例)。


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

    自 5.1.1 版本以来,代码库已经看到很多贡献,其中包含多个可空映射的修复(例如 thisthis 拉取请求)。我认为其中一项更改已解决此问题。

    很可能是拉取请求#1672,它只是为了删除不需要的代码,但显然也修复了issue 1664,这是关于 AutoMapper 显然优先于非可空源的可空源映射,即使是不可空源映射。这听起来很像您遇到的这个问题。

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

    【讨论】:

    • 非常感谢您的回答。我不想使用 alpha 版本,所以我尝试了你建议的解决方法,但我得到了一个例外,说 Coalesce used with type that cannot be null
    • 嗯,你是对的。在这里,AutoMapper 再次使用 int? =&gt; int 映射而不是 int =&gt; int 映射。不幸的是,它似乎无法通过手动定义另一个 int =&gt; int 映射来解决。猜猜目前没有解决方法。
    • 没问题,我暂时使用了 Alpha 版本。再次感谢您的出色回答!
    猜你喜欢
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多