使用 C# 中另一个 POCO 的非默认值填充 POCO

Populate POCO with non-default values from another POCO in C#

我有一个外部 API 在 XML 中发送对象。在每次更新时,API 仅发送自上次更新以来更改过的字段。是否有一个库可以使用非默认值或从新实例更改的值更新现有的 POCO 实例?

我在另一个项目中使用了AutoMapper,但它要么不支持这种方法,要么我是盲人,无法理解如何将其用于此目的。我知道我可以使用 AutoMapper 创建非常复杂的手动映射,但可能有一种使用动态映射的自动方法?

建议的 similar answer 引导了正确的方向。有一些 object 类型相等的陷阱,但链接答案是关键。

以下是完全可重现的单元测试:

using System;
using NUnit.Framework;
using AutoMapper;

namespace MyTests {
    [TestFixture]
    public class AutomappingTests {

        public class IncrementalPOCO
        {
            public int ValueType { get; set; }
            public string RefernceType { get; set; }
        }

        private Func<IncrementalPOCO> getOriginal = () => new IncrementalPOCO()
        {
            ValueType = 123,
            RefernceType = "original text"
        };

        private IncrementalPOCO updateValue = new IncrementalPOCO()
        {
            ValueType = 456
        };

        private IncrementalPOCO updateText = new IncrementalPOCO()
        {
            RefernceType = "updated text"
        };

        private IncrementalPOCO updateWithDefault = new IncrementalPOCO()
        {
            ValueType = 0,
            RefernceType = null
        };


        public static bool Always(object value)
        {
            return true;
        }

        public static bool IsDefault(Type type, object value) {
            if (type.IsValueType) {
                return Activator.CreateInstance(type).Equals(value);
            }
            return null == value;
        }

        public static bool AreDifferent(Type type, object source, object destination) {
            if (type.IsValueType) {
                return !source.Equals(destination);
            }
            return !ReferenceEquals(source, destination);
        }

        [Test]
        public void CouldUpdateValues() {
            // Updates all properties
            var original = getOriginal();
            var map = Mapper.CreateMap<IncrementalPOCO, IncrementalPOCO>();

            map.ForAllMembers(opt => opt.Condition(srs => Always(srs.SourceValue))); // just for overload resolution, `true` doesn't work

            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateValue, original);
            Assert.AreEqual(null, original.RefernceType);
            Assert.AreEqual(updateValue.ValueType, original.ValueType);

        }

        [Test]
        public void CouldUpdateNonDefaultValues()
        {
            // should work for value types and reference types (nulls)
            var original = getOriginal();
            var map = Mapper.CreateMap<IncrementalPOCO, IncrementalPOCO>();
            // NB != or simple equility won't work
            map.ForAllMembers(opt => opt.Condition(srs => !(IsDefault(srs.SourceType, srs.SourceValue))) );

            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateValue, original);
            Assert.AreEqual("original text", original.RefernceType);
            Assert.AreEqual(updateValue.ValueType, original.ValueType);

            original = getOriginal();
            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateText, original);
            Assert.AreEqual("updated text", original.RefernceType);
            Assert.AreEqual(123, original.ValueType);

            // this mapping should not change the original because all new values are default
            original = getOriginal();
            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateWithDefault, original);
            Assert.AreEqual("original text", original.RefernceType);
            Assert.AreEqual(123, original.ValueType);
        }

        [Test]
        public void CouldUpdateChangedValues() {
            // should update value types when new value is a default one, but different from origin
            var original = getOriginal();
            var map = Mapper.CreateMap<IncrementalPOCO, IncrementalPOCO>();
            // NB != or simple equility won't work
            map.ForAllMembers(opt => opt.Condition(srs => (AreDifferent(srs.SourceType, srs.SourceValue, srs.DestinationValue))));

            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateValue, original);
            Assert.AreEqual(null, original.RefernceType);
            Assert.AreEqual(updateValue.ValueType, original.ValueType);

            original = getOriginal();
            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateText, original);
            Assert.AreEqual("updated text", original.RefernceType);
            Assert.AreEqual(0, original.ValueType);

            // this mapping will change the original because all new values are default but different from original
            original = getOriginal();
            Mapper.Map<IncrementalPOCO, IncrementalPOCO>(updateWithDefault, original);
            Assert.AreEqual(null, original.RefernceType);
            Assert.AreEqual(0, original.ValueType);
        }

    }
}