AutoMapper 在目标是记录的映射源中抛出具有名为“Original”属性的 NullReferenceException

问题描述 投票:0回答:1

我在映射中得到

NullReferenceException
,其中源类型具有名为“Original”的属性,并且映射的目标是记录(而不是类)。这是 AutoMapper 版本 12.0.1 的情况。

根据 Lucien 的评论,我添加了对 AssertConfigurationIsValid 的调用,这表明该属性正在映射到构造函数参数。

The following member on AutoMapperIssues.OriginalPropertyMappedToRecordIssue+TypedConstructorDestination cannot be mapped: 
    Void .ctor(TypedConstructorDestination), parameter original 
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type AutoMapperIssues.OriginalPropertyMappedToRecordIssue+TypedConstructorDestination.
Context:
    Mapping to member Void .ctor(TypedConstructorDestination), parameter original from AutoMapperIssues.OriginalPropertyMappedToRecordIssue+Source to AutoMapperIssues.OriginalPropertyMappedToRecordIssue+TypedConstructorDestination
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.

在调查时,我尝试定义这样的记录。

private record TypedPrimaryConstructorDestination(TypedPrimaryConstructorDestination Original);

这导致了以下构建错误。

Error   CS8910  The primary constructor conflicts with the synthesized copy constructor.

涵盖错误的文档包含以下文本。

将记录修饰符添加到结构或类类型会创建一条记录。记录包含编译器合成的复制构造函数。

该链接指向更多文档,涵盖记录的非破坏性突变

以下 xUnit 测试类重现了该问题并演示了以下解决方法。进行任何一项更改,就不再引发异常。

  1. 将映射的目标设置为类而不是记录。
  2. 对作为映射源的类中的属性使用“Original”以外的名称。即使是“Original1”也可以。
  3. 将已转换为目标类型的 null 值映射到名为“original”的构造函数参数。
  4. 将源中 Original 属性的类型更改为目标类型。
  5. 添加一个具有与源属性相同类型的单个参数的主构造函数。
  6. 添加一个具有与源属性相同类型的单个参数的构造函数。
using AutoMapper;

namespace AutoMapperIssues
{
    public class OriginalPropertyMappedToRecordIssue
    {
        [Fact]
        //This very specific scenario results in a NullReferenceException during mapping.
        //It also results in an AutoMapperConfigurationException exception when AssertConfigurationIsValid is called.
        //I posted on StackOverflow which is the first step in raising an AutoMapper GitHub issue.
        //https://stackoverflow.com/questions/77316677/automapper-throws-nullreferenceexception-with-property-named-original-in-mappi
        //It looks like it is the automatically generated (synthesized) copy constructors in records that are at the core of this.
        //https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/constructor-errors?f1url=%3FappId%3Droslyn%26k%3Dk(CS8910)#records-and-copy-constructors
        public void Reproduction1()
        {
            Assert.Throws<AutoMapperConfigurationException>(() => TestSourceAndDestinationCombination<Source, Destination>());
        }

        [Fact]
        //Added a constructor with a single parameter named original typed as the destination.
        public void Reproduction2()
        {
            Assert.Throws<AutoMapperConfigurationException>(() => TestSourceAndDestinationCombination<Source, TypedConstructorDestination>());
        }

        private record TypedConstructorDestination
        {
            public TypedConstructorDestination(TypedConstructorDestination original)
            {
            }
        }

        //[Fact]
        //public void Reproduction3()
        //{
        //    TestSourceAndDestinationCombination<Source, TypedPrimaryConstructorDestination>();
        //}
        //private record TypedPrimaryConstructorDestination(TypedPrimaryConstructorDestination Original);

        private static void TestSourceAndDestinationCombination<TSource, TDestination>(Action<IMappingExpression<TSource, TDestination>>? configure = null) where TSource : new()
        {
            var mapperConfiguration = new MapperConfiguration(
                cfg =>
                {
                    var mappingExpression = cfg.CreateMap<TSource, TDestination>();

                    configure?.Invoke(mappingExpression);
                }
            );

            mapperConfiguration.AssertConfigurationIsValid();

            var mapper = mapperConfiguration.CreateMapper();

            var source = new TSource();

            mapper.Map<TDestination>(source);
        }

        private record Source { public bool Original { get; init; } }

        private record Destination;

        [Fact]
        //Changed the destination from a record to a class.
        public void Workaround1()
        {
            TestSourceAndDestinationCombination<Source, ClassDestination>();
        }

        private class ClassDestination { }

        [Fact]
        //Renamed the property named 'Original' in the source.
        public void Workaround2()
        {
            TestSourceAndDestinationCombination<RenamedPropertySource, Destination>();
        }

        private record RenamedPropertySource { public bool Original1 { get; init; } }

        [Fact]
        //Map a null value which has been cast to the destination type to a constructor parameter named 'original'.
        public void Workaround3()
        {
            TestSourceAndDestinationCombination<Source, Destination>(
                expression => expression.ForCtorParam(
                    "original",
                    options => options.MapFrom(
                        source => (Destination?)null
                    )
                )
            );
        }

        [Fact]
        //Changed the type of the Original property in the source to be the destination type.
        public void Workaround4()
        {
            TestSourceAndDestinationCombination<PropertyTypedAsDestinationSource, Destination>();
        }

        private record PropertyTypedAsDestinationSource { public Destination Original { get; init; } }

        [Fact]
        //Added a primary constructor with a single parameter with the same type as the source property.
        public void Workaround5()
        {
            TestSourceAndDestinationCombination<Source, PrimaryConstructorDestination>();
        }

        private record PrimaryConstructorDestination(bool Original);

        [Fact]
        //Added a constructor with a single parameter with the same type as the source property.
        public void Workaround7()
        {
            TestSourceAndDestinationCombination<Source, ConstructorDestination>();
        }

        private record ConstructorDestination{
            ConstructorDestination(bool original)
            {
            }
        }
    }
}

这对我来说看起来像是一个错误。 AutoMapper GitHub 存储库 中的说明要求在将其作为问题提交之前在 StackOverflow 上进行确认。所以请帮忙确认一下这是一个bug。

如果您是 AutoMapper 新手,请先在 StackOverflow 上提问,如果那里的人认为这是一个错误,请返回此处。

automapper
1个回答
0
投票

一个简单的解决方案是仅使用公共构造函数:

var configuration = new MapperConfiguration(cfg => cfg.ShouldUseConstructor = constructor => constructor.IsPublic);
© www.soinside.com 2019 - 2024. All rights reserved.