映射两个现有对象时跳过空列表

时间:2018-03-20 09:30:32

标签: c# automapper

致力于支持遗留代码,基本上我需要的是将两个对象合并在一起。一个是来自UI,一切都是null或空集合(我不想要那个),第二个是相同的,从数据库新鲜。我想覆盖从源到目的地的非空,非空值,保持其他一切完整。我这样做:

Mapper.Map(model, result);

我使用以下方法解决不需要的属性:

    cfg.CreateMap<ClientItem, ClientItem>().ForAllMembers(a =>
            {
                a.ResolveUsing<IgnoreNullSourceValues, object>(a.DestinationMember.Name);
            });

我的自定义类是:

class IgnoreNullSourceValues : IMemberValueResolver<object, object, object, object>
    {
        public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
        {
            if (sourceMember is IList)
            {
                var a = (IList)sourceMember;
                if (a.Count == 0)
                    return destinationMember;
            }

            if (sourceMember is DateTime)
            {
                var a = (DateTime)sourceMember;
                if (a == DateTime.MinValue)
                    return destinationMember;
            }
            return sourceMember ?? destinationMember;
        }
    }

它适用于所有内容,但是当我调试时,我看到例如sourceMember上的空List,destinationMember中的好列表,我看到它返回destinationMember,但是在setter中我看到它然后绑定空列表。我怎么能克服这个?

1 个答案:

答案 0 :(得分:0)

问题

AutoMapper似乎在调用Clear()之后但在设置新值之前调用目标列表上的Resolve()。这意味着destinationMember的值实际上会在调用setter之前发生变化。您可以(粗略地)通过将IList的类型更改为ObservableCollection并监听目标集合上的更改来确认这一点。例如:

var clearedCollection = new ObservableCollection<int>() { 1, 2, 3 };
clearedCollection.CollectionChanged += ClearedCollection_CollectionChanged;

var model = new ClientItem() { Test = new ObservableCollection<int>() };
var result = new ClientItem() { Test = clearedCollection };

Mapper.Map(model, result);
class ClientItem
{
    private ObservableCollection<int> test;
    public ObservableCollection<int> Test
    {
        get
        {
            return test;
        }
        set
        {
            Console.Out.WriteLine("Set");
            test = value;
        }
    }
}
private static void ClearedCollection_CollectionChanged(object sender, 
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    Console.Out.WriteLine(e.Action);
}
public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
{
    Console.Out.WriteLine("Resolve");
    ...
}

将输出

Set
Set
Resolve
Reset
Add
Add
Add
Set

前两个set用于初始化ClientItems。调用Resolve后,目标为Reset,以便从Resolve返回的列表中的项目可以Add。由于从Resolve返回的列表与Reset相同,因此无需添加任何内容。最后set是将此空白列表添加回ClientItem的位置(这是您在调试时看到的内容)。

解决方案

为了确保在操作过程中集合保持完整,您可以复制返回的值:

class IgnoreNullSourceValues : IMemberValueResolver<object, object, object, 
object>
{
    public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
    {
        if (destinationMember is IList)
        {
            var a = (IList)sourceMember;
            if (a == null || a.Count == 0)
                return CopyIList(destinationMember as IList);
        }

        if (sourceMember is DateTime)
        {
            var a = (DateTime)sourceMember;
            if (a == DateTime.MinValue)
                return destinationMember;
        }
        return sourceMember ?? destinationMember;
    }

    private static IList CopyIList(IList list)
    {
        var ret = (IList)Activator.CreateInstance(list.GetType());
        foreach (var item in list)
            ret.Add(item);
        return ret;
    }
}

请注意,在上面的代码中,我将第一个if语句切换为destinationMember而不是sourceMember。这是因为如果sourceMember为空,if语句将评估为false,即使它是IList。在你的代码中这很好,但是在这种情况下需要调用copy方法。

此解决方法似乎表明这可能不是预期的行为。我可能会在AutoMapper问题跟踪器中提出问题,看看会发生什么变化。