【问题标题】:AutoMapper - Different ProjectUsings for nullable and non-nullable intsAutoMapper - 可空和不可空整数的不同 ProjectUsings
【发布时间】:2016-08-17 05:48:58
【问题描述】:

我需要使用 Entity Framework 和 AutoMapper 将数据库中的整数值投影到枚举值。 问题似乎是这些列在某些情况下可以为空,而在其他情况下则不可为空。 如果它们是可空类型,我想为空值使用默认值(第一个枚举值)。

这是一个完整的、最小的示例,我结合当前的方法。 它是一个控制台应用程序,安装了 Entity Framework 和 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。 它似乎适用于该版本中定义的两个地图。

【问题讨论】:

    标签: c# entity-framework automapper


    【解决方案1】:

    当前 AutoMapper MapperConfiguration 类中存在(恕我直言)一个错误,该错误导致 Nullable&lt;TSource&gt; -&gt; TDestination 的指定映射用于(覆盖已指定的)TSource -&gt; 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 尝试使用指定的表达式将 TSource 映射到 TDestination(在您的情况下,int 到 MyEnum 用于 EnumValue 属性)时,结果是上述运行时异常(因为 @987654331 @ 是 int,而不是预期的 int?,因此没有 HasValue 和 Value 属性。

    作为一种解决方法(或通用解决方案?)我建议只定义从int 到enum 的地图,并为另一部分使用AutoMapper Null substitution 功能。由于目前仅在属性映射级别支持 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);
        }
    }
    

    【讨论】:

    • 谢谢,这很完美,看起来对我来说是一个可靠的解决方案!我将在 AutoMapper 存储库上创建一个 GitHub 问题,询问他们是否可以确认这是一个错误以及是否会修复它。
    【解决方案2】:

    或者,您可以尝试像这样进行映射:

    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));
    

    【讨论】:

    • 感谢您的回答 - 这会起作用,但我更喜欢更通用的解决方案,因为这只是从一个更大的项目中获取的示例,该项目在多个地方使用了此投影。我可能会错过一些单元测试未涵盖的属性,并且在生产之前可能会被忽视。当然,更通用的解决方案也会更干燥,这将是一个巨大的优势。不过感谢您的想法!
    • 非常正确。我会看看能不能想出一个更通用的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2019-08-12
    • 2021-08-31
    • 1970-01-01
    • 2016-01-22
    • 2014-09-12
    • 1970-01-01
    • 1970-01-01
    • 2020-05-19
    相关资源
    最近更新 更多