2

I'm using automapper to update properties of an object; "merging" two objects but only if the source member is != null. (According to this question)

Apparently it does not work with Enums. After calling MergeObject method, it maps to the enum default's value (Pendente) instead of ignoring it and leaving destination as it is.

It works fine for nullable int, for exemple.

When debugging, if I set a breakpoint on the condition (=> member != null), it'll show member = Pendente, even when Source.Situacao is null.

enter image description here

Hitting F10 goes to the next member but you can see that it changed destination.Situacao value.

enter image description here

It seems like a bug to me, but my issue was closed. Any thoughts?

public class FooViewModel
{
    public Guid Id { get; set; }
    public Status? Situacao { get; set; }
}

public class FooModel
{
    public Guid Id { get; set; }
    public Status Situacao { get; set; }
}

public enum Status
{
    Pendente,
    EmProcessamento,
    Processada
}

private static void Initialize()
{
    Mapper.Initialize(cfg =>
    {
        cfg.CreateMap<FooViewModel, FooModel>()
          .ForAllMembers(o => o.Condition((source, destination, member) => member != null));
    });
}

public static void MergeObject(FooViewModel source, FooModel destination)
{
    Mapper.Map(source, destination);
}

EDIT1: My goal is to achieve basically the functionality described in here but I can't see the property IsSourceValueNull.

EDIT2: I managed to achieve my goal using the following line but I had to specify the member explicitly. Is there a generic way to apply this to all members?

cfg.CreateMap<FooViewModel, Foo>()
                    .ForMember(dest => dest.Situacao, opt => opt.MapFrom((source, dest) =>  source.Situacao ?? dest.Situacao));
Matheus Lemos
  • 578
  • 4
  • 13

2 Answers2

4

It looks like your enum isn't nullable in your second type.

public class FooModel
{
    public Guid Id { get; set; }
    public Status Situacao { get; set; }
}

should be

public class FooModel
{
    public Guid Id { get; set; }
    public Status? Situacao { get; set; }
}

Also, I would highly recommend setting a safe default value for your enum, example:

public enum Status
{
    None,
    Pendente,
    EmProcessamento,
    Processada
}
John
  • 1,240
  • 9
  • 13
  • That's why I created the mapping to map only members that are != null – Matheus Lemos May 10 '19 at 16:18
  • 1
    The mapped value's type isn't nullable, so it will default to the first enum value. If you swap the values in the enum it will just pick the first every time. What behavior would you expect otherwise? – John May 10 '19 at 16:22
  • I expected it to skip the mapping for this member, according to the condition, relative to the value in source. Isn't that the way it should work? – Matheus Lemos May 10 '19 at 16:30
  • Automapper is skipping that property most likely. Do a new FooModel() and tell me what the value of Situacao is on instantiation. You'll see your brand new FooModel will pick the default value of the enum. If you want it to be null, or some other default value, you'll need to define it. – John May 10 '19 at 16:34
  • I know that a enum default value will be the first. That is not the point. My goal is to prevent it from overriding the value on destination to default, if the value in source is null, leaving destination as it is. – Matheus Lemos May 10 '19 at 16:39
  • 1
    It is in fact changing the property on the target object, it is not skipping it altogether. This has nothing to do with default value for enum properties. It may be that when the source property is null (so it doesn't match the condition), the value to store into the target property is the default for that enum, but the problem is not that the property on the target object defaults to a value for that enum, the problem is that the default is in fact written to the property by the mapper. – Lasse V. Karlsen May 10 '19 at 16:40
  • @MatheusLemos, your member's conditional != null is an impossible state to reach because FooModel.Situacao is not a nullable type. Reserve the mapping order or make the value a nullable type. – John May 10 '19 at 16:45
  • @LasseVågsætherKarlsen precisely. I was expecting it to keep the value present in destination, instead of placing default if the condition is not matched. – Matheus Lemos May 10 '19 at 16:48
  • It seems the translation to the target property type value has already been made, the `member` value at that point is already the default value. – Lasse V. Karlsen May 10 '19 at 16:51
  • For your original config, you could have just used the .ReverseMap() or cfg.CreateMap() – John May 10 '19 at 17:25
  • This still doesn't change the fact that you have an unsafe default mapping. – John May 10 '19 at 17:26
  • @JohnD how come reversing the map would solve my problem if I need to map from ViewModel to Model and **not** from Model to ViewModel? – Matheus Lemos May 10 '19 at 17:28
  • @MatheusLemos, because the conditional null check you were doing was against a non-nullable type with the original mapping configuration. Swapping the mapping order will allow you to do the conditional against the nullable FooViewModel's public Status? Situacao. You should just make them both nullable and/or set a safe default value. – John May 10 '19 at 17:31
  • I think you're not getting the point. I need to apply to all members what I applied to the member explicitly, as shown in second edit. I don't see how reversing the map would achieve that. – Matheus Lemos May 10 '19 at 17:37
2

Ended up using the solution in my second edit, thanks everyone for the input.

cfg.CreateMap<FooViewModel, Foo>()
                .ForMember(dest => dest.Situacao, opt => opt.MapFrom((source, dest) =>  source.Situacao ?? dest.Situacao));
Matheus Lemos
  • 578
  • 4
  • 13