当我使用Condition时,为什么我得到“Nullable对象必须有值”异常?

时间:2012-12-03 22:01:02

标签: automapper

我在下面的示例代码中得到一个“Nullable对象必须有值”。为什么我需要以下修复才能使其工作:

.ForMember(dest => dest.ShirtColor,
           dest => dest.MapFrom(src => src.ShirtColor != null
                                       ? new OptionSetValue((int) src.ShirtColor)
                                       : null))
AutoMapper.Mapper.CreateMap<PersonA, PersonB>()
    .ForMember(dest => dest.FirstName, dest => dest.MapFrom(src => src.FirstName))
    .ForMember(dest => dest.LastName, dest => dest.MapFrom(src => src.LastName))

    // Condition to avoid overwriting existing data!!!
    .ForMember(dest => dest.ShirtColor,
               dest => dest.Condition(src => src.ShirtColor != null))
    .ForMember(dest => dest.ShirtColor,
               dest => dest.MapFrom(
                   src => new OptionSetValue((int)src.ShirtColor)))
    // Fix that should not be needed due to condition:
    //.ForMember(dest => dest.ShirtColor,
    //           dest => dest.MapFrom(
    //               src => src.ShirtColor != null
    //                      ? new OptionSetValue((int) src.ShirtColor)
    //                      : null));


PersonA source = new PersonA();
source.FirstName = "Thomas";
source.LastName = "Jefferson";
source.ShirtColor = null;  // nullable int

PersonB destination = new PersonB();
destination.FirstName = "Thomas";
destination.LastName = "Jefferson";
destination.ShirtColor = new OptionSetValue(4);

// Results in: "Nullable object must have a value" despite the fact that
// condition should have been met!
Mapper.Map<PersonA, PersonB>(source, destination);

Debug.Assert(destination.ShirtColor != null);
Console.WriteLine("Our existing data was not overwritten!!");

Console.WriteLine("Hit enter to exit");
Console.ReadLine();

以下是OptionSet的定义方式:     公共类OptionSetValue     {         public OptionSetValue(){}

    public OptionSetValue(int value)
    {
        Number = value;
    }

    public int Number { get; set; }
}

3 个答案:

答案 0 :(得分:3)

您需要更改

.ForMember(dest => dest.ShirtColor,
           dest => dest.Condition(src => src.ShirtColor != null))

.ForMember(dest => dest.ShirtColor,
           opt => opt.Condition(src => !src.IsSourceValueNull))

请注意,您也不需要以下内容,因为它们按惯例映射:

.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName))

更新 - 2

问题与条件和映射尚未组合的事实有关。我已经创建了一个简单的扩展方法来允许它。

这是一个演示它的测试

<强>类

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class PersonA : Person
{
    public int? ShirtColor { get; set; }
}

public class PersonB : Person
{
    public OptionSetValue ShirtColor { get; set; }
}

public class OptionSetValue
{
    public int OptionSet { get; set; }
}

<强> MappingConfiguration

public class MyProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<PersonA, PersonB>()
              .ForMember(dest => dest.ShirtColor,
                         opt => opt.ConditionallyMapFrom(
                             src => new OptionSetValue
                                 {
                                     OptionSet = src.ShirtColor.Value
                                 },
                             src => src.ShirtColor.HasValue));
    }
}

public static class AutoMapperExtensions
{
    public static void ConditionallyMapFrom<TSource, TMember>(
                         this IMemberConfigurationExpression<TSource> expression,
                         Expression<Func<TSource, TMember>> sourceMember,
                         Func<TSource, bool> condition)
    {
        expression.Condition(condition);
        expression.MapFrom(sourceMember);
    }
}

<强>单元测试

[TestFixture]
public class MappingTests
{
    [Test]
    public void AutoMapper_Configuration_IsValid()
    {
        Mapper.Initialize(m => m.AddProfile<MyProfile>());
        Mapper.AssertConfigurationIsValid();
    }

    [Test]
    public void AutoMapper_ClientMapping_IsValid()
    {
        Mapper.Initialize(m => m.AddProfile<MyProfile>());
        Mapper.AssertConfigurationIsValid();

        var source = new PersonA
            {
                FirstName = "FirstA",
                LastName = "LastA",
                ShirtColor = null
            };

        var destination = new PersonB
            {
                FirstName = "FirstB",
                LastName = "LastB"
            };

        destination = Mapper.Map(source, destination);

        Assert.That(destination, Is.Not.Null);
        Assert.That(destination.FirstName, Is.EqualTo("FirstA"));
        Assert.That(destination.LastName, Is.EqualTo("LastA"));
        Assert.That(destination.ShirtColor, Is.Null);
    }
}

答案 1 :(得分:2)

只需将其转换为可以为空的 int

.ForMember((destination)         => destination.ShirtColor,
           (memberConfiguration) => memberConfiguration.MapFrom((source) => (int?)source.ShirtColor))

答案 2 :(得分:2)

只需使用PreCondition代替Condition ..

所以改变你的

//Condition to avoid overwriting existing data!!!
.ForMember(dest => dest.ShirtColor, dest => dest.Condition(src => src.ShirtColor != null))
    .ForMember(dest => dest.ShirtColor, dest => dest.MapFrom(src => new OptionSetValue((int)src.ShirtColor)))

//Condition to avoid overwriting existing data!!!
.ForMember(dest => dest.ShirtColor, dest => dest.PreCondition(src => src.ShirtColor != null))
    .ForMember(dest => dest.ShirtColor, dest => dest.MapFrom(src => new OptionSetValue((int)src.ShirtColor)))