【问题标题】:AutoMapper flattening of nested mappings asks for a custom resolver嵌套映射的 AutoMapper 展平需要自定义解析器
【发布时间】:2023-04-03 02:40:01
【问题描述】:

我对 AutoMapper 有点陌生,想将 POCO-ish 对象映射到可能更复杂的 DTO,后者试图成为 Google Books API's Volume 资源的表示:

Book.cs

public class Book
{
    public string Isbn10 { get; set; }
    public string Isbn13 { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public string Publisher { get; set; }
    public DateTime Publication { get; set; }
    public int Pages { get; set; }
    public string Description { get; set; }
    public bool InStock { get; set; }
}

BookDto.cs

public class BookDto
{
    public string Kind { get; set; }
    public string Id { get; set; }
    public VolumeInfo VolumeInfo { get; set; }
}

public class VolumeInfo
{
    public string Title { get; set; }
    public List<string> Authors { get; set; }
    public string Publisher { get; set; }
    public string PublishedDate { get; set; }
    public string Description { get; set; }
    public int PageCount { get; set; }
    public List<IndustryIdentifier> IndustryIdentifiers { get; set; }
}

public class IndustryIdentifier
{
    public string Type { get; set; }
    public string Identifier { get; set; }
}

所以根据documentation 我们可以简单地展平嵌套类型:

AutoMapperConfigurator.cs

public static class AutoMapperConfigurator
{
    public static void Configure()
    {
        Mapper.CreateMap<Book, BookDto>()
            .ForMember(dto => dto.Id, options => options.Ignore())
            .ForMember(dto => dto.Kind, options => options.Ignore())
            .ForMember(dto => dto.VolumeInfo.Title, options => options.MapFrom(book => book.Title))
            .ForMember(dto => dto.VolumeInfo.Authors, options => options.MapFrom(book => book.Author.ToArray()))
            .ForMember(dto => dto.VolumeInfo.Publisher, options => options.MapFrom(book => book.Publisher))
            .ForMember(dto => dto.VolumeInfo.PublishedDate, options => options.MapFrom(book => book.Publication))
            .ForMember(dto => dto.VolumeInfo.Description, options => options.MapFrom(book => book.Description))
            .ForMember(dto => dto.VolumeInfo.PageCount, options => options.MapFrom(book => book.Pages))
            ;
    }
}

但不幸的是,在运行 Mapper.AssertConfigurationIsValid() 测试时,我收到以下错误:

System.ArgumentException:表达式'dto => dto.VolumeInfo.Title' 必须解析为顶级成员,而不是任何子对象的 特性。在子类型或 AfterMap 上使用自定义解析器 选项。参数名称:lambdaExpression

所以现在按照这个建议尝试使用 AfterMap:

public static class AutoMapperConfigurator
{
    public static void Configure()
    {
        Mapper.CreateMap<Book, BookDto>()
            .ForMember(dto => dto.Id, options => options.Ignore())
            .ForMember(dto => dto.Kind, options => options.Ignore())
            .AfterMap((book, bookDto) => bookDto.VolumeInfo = new VolumeInfo 
                { 
                    Title = book.Title,
                    Authors = new List<string>(){ book.Author },
                    Publisher = book.Publisher,
                    PublishedDate = book.Publication.ToShortDateString(),
                    Description = book.Description,
                    PageCount = book.Pages
                });
    }
}

再次运行测试时,我现在收到以下消息:

找到未映射的成员。查看下面的类型和成员。添加一个 自定义映射表达式,忽略,添加自定义解析器,或修改 源/目标类型 Book -> BookDto(目标成员列表) Dotnet.Samples.AutoMapper.Book -> Dotnet.Samples.AutoMapper.BookDto (目标成员列表)VolumeInfo

我应该在嵌套类之间创建额外的映射吗?任何指导将不胜感激,在此先感谢。

【问题讨论】:

    标签: .net mapping automapper dto


    【解决方案1】:

    在使用带有内部映射的 VolumnInfo 映射的 .ForMember 之前,我做过类似的事情:

    public static class AutoMapperConfigurator
    {
        public static void Configure()
        {
            Mapper.CreateMap<Book, VolumeInfo>()
                .ForMember(dto => dto.Authors, options => options.MapFrom(book => book.Author.Split(',')))
                .ForMember(dto => dto.PublishedDate, options => options.MapFrom(book => book.Publication))
                .ForMember(dto => dto.PageCount, options => options.MapFrom(book => book.Pages))
                .ForMember(dto => dto.IndustryIdentifiers, options => options.Ignore());
    
            Mapper.CreateMap<Book, BookDto>()
                .ForMember(dto => dto.Id, options => options.Ignore())
                .ForMember(dto => dto.Kind, options => options.Ignore())
                .ForMember(dto => dto.VolumeInfo, options => options.MapFrom(book => Mapper.Map<Book, VolumeInfo>(book)));
        }
    }
    

    这里有几个用于验证功能的单元测试:

    [TestFixture]
    public class MappingTests
    {
        [Test]
        public void AutoMapper_Configuration_IsValid()
        {
            AutoMapperConfigurator.Configure();
            Mapper.AssertConfigurationIsValid();
        }
    
        [Test]
        public void AutoMapper_MapsAsExpected()
        {
            AutoMapperConfigurator.Configure();
            Mapper.AssertConfigurationIsValid();
    
            var book = new Book
                {
                    Author = "Castle,Rocks",
                    Description = "Awesome TV",
                    InStock = true,
                    Isbn10 = "0123456789",
                    Isbn13 = "0123456789012",
                    Pages = 321321,
                    Publication = new DateTime(2012, 11, 01),
                    Publisher = "Unknown",
                    Title = "Why I Rock"
                };
    
            var dto = Mapper.Map<Book, BookDto>(book);
    
            Assert.That(dto.Id, Is.Null);
            Assert.That(dto.Kind, Is.Null);
            Assert.That(dto.VolumeInfo, Is.Not.Null);
            Assert.That(dto.VolumeInfo.Authors, Is.Not.Null);
            Assert.That(dto.VolumeInfo.Authors.Count, Is.EqualTo(2));
            Assert.That(dto.VolumeInfo.Authors[0], Is.EqualTo("Castle"));
            Assert.That(dto.VolumeInfo.Authors[1], Is.EqualTo("Rocks"));
            Assert.That(dto.VolumeInfo.Description, Is.EqualTo("Awesome TV"));
            Assert.That(dto.VolumeInfo.IndustryIdentifiers, Is.Null);
            Assert.That(dto.VolumeInfo.PageCount, Is.EqualTo(321321));
            Assert.That(dto.VolumeInfo.PublishedDate, Is.EqualTo(new DateTime(2012, 11, 01).ToString()));
            Assert.That(dto.VolumeInfo.Publisher, Is.EqualTo("Unknown"));
            Assert.That(dto.VolumeInfo.Title, Is.EqualTo("Why I Rock"));
        }
    }
    

    【讨论】:

    • 它有效 - 非常感谢!所以基本上我必须为每个嵌套有一个映射 - 例如对于IndustryIdentifiers,我也应该去Mapper.CreateMap&lt;VolumeInfo, IndustryIdentifier&gt;()
    • 你没有指定你从哪里得到类型和标识符,但我怀疑它来自Book。因此,您将需要Mapper.CreateMap&lt;Book, IndustryIdentifier&gt;() 之类的东西,并更新上面的.ForMember(dto =&gt; dto.IndustryIdentifiers... 映射以调用options.MapFrom(book =&gt; Mapper.Map&lt;Book, IndustryIdentifiers&gt;(book))。这并不完全正确,因为您将其定义为列表,但您应该明白这一点。
    • 是的,这正是我所做的,因此遇到了您提到的列表问题。
    • 如果你告诉我你想从哪里得到TypeIdentifier,我会看看配置
    • 根据文档,例如Isbn13 Type 应该是“ISBN_13”,Identifier 应该是数字本身。我想我已经解决了,如果你想看一下,这里是 sn-p:raw.github.com/nanotaboada/dotnet/master/…
    猜你喜欢
    • 1970-01-01
    • 2020-07-17
    • 2014-08-05
    • 1970-01-01
    • 2017-02-17
    • 1970-01-01
    • 1970-01-01
    • 2014-03-27
    • 1970-01-01
    相关资源
    最近更新 更多