Automapper - 使 IncludeMembers() 忽略 null

Automapper - Make IncludeMembers() ignore null

IncludeMembers() 将始终从第一个匹配开始映射,即使对象是 null.

假设我们有以下源模型:

public class Item
{
    public MovieMetadata MovieMetadata { get; set; }

    public BookMetadata BookMetadata { get; set; }
}

public class MovieMetadata
{
    public string Title { get; set; }
}

public class BookMetadata
{
    public string Title { get; set; }
}

目标模型:

public class ItemDetail
{
    public string Title { get; set; }
}

映射配置文件:

public class ItemProfile : Profile
{
    public ItemProfile()
    {
        CreateMap<Item, ItemDetail>()
            .IncludeMembers(
            src => src.BookMetadata,
            src => src.MovieMetadata);

        CreateMap<BookMetadata, ItemDetail>();

        CreateMap<MovieMetadata, ItemDetail>();
    }
}

以及具有实例化和测试逻辑的程序class:

public class Program
{
    public static void Main()
    {
        //create mapper
        var configuration = new MapperConfiguration(cfg =>
        {
            cfg.AddMaps(typeof(ItemProfile));
        });
        var mapper = configuration.CreateMapper();
        
        //check if configuration valid
        mapper.ConfigurationProvider.AssertConfigurationIsValid();
        Console.WriteLine("Mapper configuration is valid");
        
        //try map book metadata
        var bookItem = new Item
        {
            BookMetadata = new BookMetadata()
            {
                Title = "book"
            },
            MovieMetadata = null
        };
        var bookItemDetail = mapper.Map<ItemDetail>(bookItem);
        bool isBookCorrectlyMapped = bookItem.BookMetadata.Title == bookItemDetail.Title;
        Console.WriteLine($"Book mapped correctly: {isBookCorrectlyMapped}");
        
        //try map movie metadata
        var movieItem = new Item
        {
            BookMetadata = null,
            MovieMetadata = new MovieMetadata()
            {
                Title = "movie"
            }
        };
        var movieItemDetail = mapper.Map<ItemDetail>(movieItem);
        bool isMovieCorrectlyMapped = movieItem.MovieMetadata.Title == movieItemDetail.Title;
        Console.WriteLine($"Movie mapped correctly: {isMovieCorrectlyMapped}");
    }
}

这是我们将看到的输出:

Mapper configuration is valid
Book mapped correctly: True
Movie mapped correctly: False

我们看到 BookMetadata 项的映射成功,但 MovieMetadata 项的映射失败。需要进行更新以便此测试成功。

假设它失败的原因是 IncludeMembers() 中的项目顺序:

.IncludeMembers(
            src => src.BookMetadata,
            src => src.MovieMetadata)

这里我们先是 src.BookMetadata,然后是 src.MovieMetadata。在映射期间,它将在 src.BookMetadata 中找到 Title 字段,并且将始终使用该值,即使 src.BookMetadatanull

如果是null,有没有办法跳过src.BookMetadata并使用下一个src.MovieMetadata?或者可能需要使用其他东西而不是 IncludeMembers()?

这是类似的问题:https://github.com/AutoMapper/AutoMapper/issues/3204

您可以在此处找到上面的代码以快速重现问题:https://dotnetfiddle.net/9YLGR6

找到可能的解决方案。

选项 1.IValueResolver

我们可以创建自定义值解析器。此解决方案适用于我们需要从子对象映射的少量字段。因为任何字段都需要特定的值解析器。

让我们创建 TitleResolver:

public class TitleResolver : IValueResolver<Item, ItemDetail, string>
{
    public string Resolve(Item source, ItemDetail destination, string destMember, ResolutionContext context)
    {
        if (source != null)
        {
            if (source.BookMetadata != null)
            {
                return source.BookMetadata.Title;
            }
            if (source.MovieMetadata != null)
            {
                return source.MovieMetadata.Title;
            }
        }
        return null;
    }
}

并更新 ItemProfile:

public class ItemProfile : Profile
{
    public ItemProfile()
    {
        CreateMap<Item, ItemDetail>()
            .ForMember(dest => dest.Title, opt => opt.MapFrom<TitleResolver>());
    }
}

Link 到整个代码示例:https://dotnetfiddle.net/6pfKYh

选项2.BeforeMap()/AfterMap()

如果我们的子对象有多个字段要映射到目标对象,那么使用 BeforeMap()AfterMap() 方法可能是个好主意。 在这种情况下 ItemProfile 将更新为:

public class ItemProfile : Profile
{
    public ItemProfile()
    {
        CreateMap<Item, ItemDetail>(MemberList.None)
            .AfterMap((src, dest, ctx) => 
            {
                if (src.BookMetadata != null)
                {
                    ctx.Mapper.Map(src.BookMetadata, dest);
                }
                else if (src.MovieMetadata != null)
                {
                    ctx.Mapper.Map(src.MovieMetadata, dest);
                }
            });

        CreateMap<BookMetadata, ItemDetail>();

        CreateMap<MovieMetadata, ItemDetail>();
    }
}

Link 到整个代码示例:https://dotnetfiddle.net/ny1yRU