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

本文关键字:int AutoMapper 投影 何正确 配置 | 更新日期: 2023-09-27 18:12:44

我有一些麻烦,让这个正确工作。我有两个类:

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 => TestClassB,因为我可以看到AutoMapper可能不知道在值为null的情况下如何处理int?。果然,事实就是如此。因此,我为int? => int设置了如下映射:

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

这就是事情变得奇怪的地方。当我添加这个映射时,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);
    }
}

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

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

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? => int映射器,尽管更明显的基于身份的映射将在这里使用。

因为每个int也是一个有效的int?,所以AutoMapper 尝试在这里使用int? => int映射器并将结果赋值给int?成员。但是,在解析该赋值时,似乎在底层有些东西没有正确工作,因此出现了异常。

似乎修复它是添加另一个映射,int? => int?的身份映射:

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

然后,这个映射被使用,并且没有异常发生(并且映射也正确地工作-与您的所有示例)。


这个问题似乎存在于当前的AutoMapper 5.1。X版本(当前是5.1.1)。好消息是,这个问题已经被解决了。如果您从myget提要中尝试当前的5.2 alpha版本,那么代码可以正常工作,没有任何问题。

自5.1.1发布以来,代码库已经看到了相当多的贡献,对可空映射进行了多个修复(例如这个和这个拉请求)。我想其中一个修改已经解决了这个问题。

最有可能的是,它是pull request #1672,这只是意味着删除不需要的代码,但显然也修复了问题1664,这是关于AutoMapper显然优先于非空源映射的可空源映射,即使一个非空源被映射。这听起来很像你所遇到的问题。

所以,现在,你可以添加上面的解决方法来将类型映射到自身或使用alpha版本,而我们等待5.2发布。