How to ignore a property based on a runtime condit

2019-05-04 16:41发布

问题:

I have a simple pair of classes which for I've set up a mapping at initialization time.

public class Order {
  public int ID { get; set; }  
  public string Foo { get; set; }
}

public class OrderDTO {
  public int ID { get; set; }
  public string Foo { get; set; }
}

...

Mapper.CreateMap<Order, OrderDTO>();

Now at a certain point I need to map an Order to an OrderDTO. BUT depending on some circumstances, I might need to ignore Foo during mapping. Let's also assume that I cannot "store" the condition in the source or destination object.

I know how I can configure the ignored properties at initialization time, but I have no idea how I could achieve such a dynamic runtime behavior.

Any help would be appreciated.

UPDATE

My use case for this behaviour is something like this. I have an ASP.NET MVC web grid view which displays a list of OrderDTOs. The users can edit the cell values individually. The grid view sends the edited data back to the server like a collection of OrderDTOs, BUT only the edited field values are set, the others are left as default. It also sends data about which fields are edited for each primary key. Now from this special scenario I need to map these "half-empty" objects to Orders, but of course, skip those properties which were not edited for each object.

The other way would be to do the manual mapping, or use Reflection somehow, but I was just thinking about if I could use AutoMapper in some way.

回答1:

I've digged into the AutoMapper source code and samples, and found that there is a way to pass runtime parameters at mapping time.

A quick example setup and usage looks like this.

public class Order {
  public int ID { get; set; }  
  public string Foo { get; set; }
}

public class OrderDTO {
  public int ID { get; set; }
  public string Foo { get; set; }
}

...

Mapper.CreateMap<Order, OrderDTO>()
  .ForMember(e => e.Foo, o => o.Condition((ResolutionContext c) => !c.Options.Items.ContainsKey("IWantToSkipFoo")));

...

var target = new Order();
target.ID = 2;
target.Foo = "This should not change";

var source = new OrderDTO();
source.ID = 10;
source.Foo = "This won't be mapped";

Mapper.Map(source, target, opts => { opts.Items["IWantToSkipFoo"] = true; });
Assert.AreEqual(target.ID, 10);
Assert.AreEqual(target.Foo, "This should not change");

In fact this looks quite "technical", but I still think there are quite many use cases when this is really helpful. If this logic is generalized according to application needs, and wrapped into some extension methods for example, then it could be much cleaner.



回答2:

Expanding on BlackjacketMack's comment for others:

In your MappingProfile, add a ForAllMaps(...) call to your constructor.

using AutoMapper;
using System.Collections.Generic;
using System.Linq;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ForAllMaps((typeMap, mappingExpression) =>
        {
            mappingExpression.ForAllMembers(memberOptions =>
            {
                memberOptions.Condition((o1, o2, o3, o4, resolutionContext) =>
                {
                    var name = memberOptions.DestinationMember.Name;
                    if (resolutionContext.Items.TryGetValue(MemberExclusionKey, out object exclusions))
                    {
                        if (((IEnumerable<string>)exclusions).Contains(name))
                        {
                            return false;
                        }
                    }
                    return true;
                });
            });
        });
    }
    public static string MemberExclusionKey { get; } = "exclude";
}

Then, for ease of use, add the following class to create an extension method for yourself.

public static class IMappingOperationOptionsExtensions
{
    public static void ExcludeMembers(this AutoMapper.IMappingOperationOptions options, params string[] members)
    {
        options.Items[MappingProfile.MemberExclusionKey] = members;
    }
}

Finally, tie it all together: var target = mapper.Map<Order>(source, opts => opts.ExcludeMembers("Foo"));



标签: c# AutoMapper