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.BookMetadata
是 null
。
如果是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
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.BookMetadata
是 null
。
如果是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