AutoMapper -可空型和不可空型的不同投影

本文关键字:空型 投影 AutoMapper | 更新日期: 2023-09-27 18:02:45

我需要从数据库到使用实体框架和AutoMapper的枚举值项目整数值。问题似乎是列在某些情况下可以为空,而在其他情况下不可为空。如果它们是可空类型,我希望为空使用默认值(第一个enum值)。

这是一个完整的,最小的例子,我把我目前的方法。它是一个控制台应用程序,安装了实体框架和AutoMapper的当前nuget包。它还需要一个数据库(首先是数据库),其中包含如下表:

CREATE TABLE [dbo].[MyTable] (
    [Id] INT PRIMARY KEY,
    [EnumValue] INT NOT NULL,
    [EnumValueNullable] INT
)
控制台应用程序的代码(c#):
public enum MyEnum
{
    Value1 = 0,
    Value2 = 1
}
public class MyTableModel
{
    public int Id { get; set; }
    public MyEnum EnumValue { get; set; }
    public MyEnum EnumValueNullable { get; set; }
}
public class MyProfile : Profile
{
    public MyProfile()
    {
        this.CreateMap<MyTable, MyTableModel>();
        this.CreateMap<int, MyEnum>().ProjectUsing(x => (MyEnum)x);
        this.CreateMap<int?, MyEnum>().ProjectUsing(x => x.HasValue ? (MyEnum)x.Value : MyEnum.Value1);
    }
}
static void Main(string[] args)
{
    var config = new MapperConfiguration(x => x.AddProfile(new MyProfile()));
    var result = new MyDataEntities().MyTable.ProjectTo<MyTableModel>(config).ToList();
}

在执行这段代码时,它告诉我HasValue不是为System.Int32类型定义的。这当然是正确的,但是我假设AutoMapper会选择为非空整数指定的版本。删除任意一个映射(对于int和int?)都没有帮助,同时删除它们或更改顺序也没有帮助。

作为旁注,我正在一个更大的项目中从3.3.1.0版本迁移AutoMapper。该版本中定义的两个map似乎都可以使用

AutoMapper -可空型和不可空型的不同投影

有(IMHO)一个错误在当前的AutoMapper MapperConfiguration类导致指定的映射为Nullable<TSource> -> TDestination被使用(覆盖已经指定的)为TSource -> TDestination。通过执行以下代码可以很容易地看到:

var config = new MapperConfiguration(x => x.AddProfile(new MyProfile()));
var m1 = config.ResolveTypeMap(typeof(int), typeof(MyEnum));
var m2 = config.ResolveTypeMap(typeof(int?), typeof(MyEnum));
Console.WriteLine(m1 == m2); // true

结果是上述运行时异常,当AutoMapper试图映射TSourceTDestination(在你的情况下,intMyEnumEnumValue属性)使用指定的表达式(因为xint,而不是int?如预期的,因此没有HasValueValue属性)。

作为变通方法(或通用解决方案?)我建议只定义从intenum的映射,并对其他部分使用AutoMapper Null替换特性。由于目前只在属性映射级别支持空替换,为了使它更容易,我将它封装在一个助手方法中,如下所示:

public static class AutoMapperExtensions
{
    public static void NullSubstitute<TSource, TDestination>(this Profile profile, TSource nullSubstitute)
        where TSource : struct
    {
        object value = nullSubstitute;
        profile.ForAllPropertyMaps(
            map => map.SourceType == typeof(TSource?) && 
                   map.DestinationPropertyType == typeof(TDestination),
            (map, config) => config.NullSubstitute(value)
        );
    }
}

,并像这样使用:

public class MyProfile : Profile
{
    public MyProfile()
    {
        this.CreateMap<MyTable, MyTableModel>();
        this.CreateMap<int, MyEnum>().ProjectUsing(x => (MyEnum)x);
        this.NullSubstitute<int, MyEnum>((int)MyEnum.Value1);
    }
}

或者您可以尝试这样做映射:

this.CreateMap<MyTable, MyTableModel>()
     .ForMember(dest => dest.EnumValue, o => o.MapFrom(src => (MyEnum)src.EnumValue))
     .ForMember(dest => dest.EnumValueNullable, o => o.MapFrom(src => src.EnumValueNullable != null ? src.EnumValueNullable.Value : MyEnum.Value1));