【问题标题】:How to merge two objects, overriding null values如何合并两个对象,覆盖空值
【发布时间】:2021-07-02 05:22:42
【问题描述】:

我有两个 Book 类的对象。是否可以合并两个覆盖空值的对象?

图书模型:

public string Author {get; set}
public string Title {get; set}
public string EAN {get; set}

第一本书数据:

Book.Author -> "Adam Wróbel"
Book.Title -> "StackOverflow Book"
Book.EAN-> "777777777"

第二本书数据:

Book.Author -> null
Book.Title -> "StackOverflow Book v2"
Book.EAN-> null

是否可以将这两个对象组合成这样的结果?

Book.Author -> "Adam Wróbel" (data from first book)
Book.Title -> "StackOverflow Book v2" (data from second book)
Book.EAN-> "777777777" (data from first book)

附言我知道我可以通过检查语句是否不为空并分配给另一个对象来比较这些字段中的每一个。但是,这似乎是解决此问题的一种相当有限的方法。

【问题讨论】:

  • 如果第二个对象包含的属性不在第一个对象中,或者保证它们始终是同一类型,您希望它的行为如何?
  • @David L 他们总是同一类型 -> 书
  • 当两个属性都有一个值时,您是否总是想要第二个属性的值(例如您的示例中的 Title)?
  • 这能回答你的问题吗? merging two objects in C#

标签: c#


【解决方案1】:

您可以尝试使用 Automapper 并将其设置为忽略自映射的空值:

var config = new MapperConfiguration(cfg => cfg.CreateMap<Book, Book>()
    .ForAllMembers(opts => opts.Condition((src, dest, member) => member != null)));
var mapper = config.CreateMapper();
var target = new Book
{
    Author = "1",
    Title = "1",
    EAN = "1"
};
var source = new Book
{
    Title = "2"
};
var book = mapper.Map(source, target);
Console.WriteLine($"{book.Author}-{book.Title}-{book.EAN}"); // prints "1-2-1"

【讨论】:

    【解决方案2】:

    最好的方法是简单地编写手动映射器。但是,如果您想要在每个不涉及构造函数的简单 POCO 案例中为您执行此操作的东西,以下将使用反射:

    • 创建一个新的输出对象
    • 如果 value 不为 null,则映射 override prop
    • 如果覆盖道具值为空,则映射基本道具
    • 并返回克隆的对象
    public static class Merger
    {
        public static T CloneAndMerge<T>(T baseObject, T overrideObject) where T : new()
        {
            var t = typeof(T);
            var publicProperties = t.GetProperties();
            
            var output = new T();
            
            foreach (var propInfo in publicProperties)
            {
                var overrideValue = propInfo.GetValue(overrideObject);
                var defaultValue = !propInfo.PropertyType.IsValueType 
                    ? null 
                    : Activator.CreateInstance(propInfo.PropertyType);
                if (overrideValue == defaultValue)
                {
                    propInfo.SetValue(output, propInfo.GetValue(baseObject));   
                }
                else 
                {
                    propInfo.SetValue(output, overrideValue);
                }
            }
            
            return output;
        }
    }
    

    请注意,这里没有优化或缓存。我不建议将它用于生产代码,但它是一种完成您正在寻找的东西的简单方法。

    【讨论】:

    • 另请注意,值类型(int、decimal、float、Guid、DateTime、structs 等)永远不会为空。
    • @Vilx- 绝对是,尽管可以为 struct props 扩展额外的检查。
    • 真的,真的。不过,这可能会很快变得非常毛茸茸。您还应该检查字段,而不仅仅是属性。避免只读属性。不确定事件支持字段。您可能需要自定义属性将某些属性标记为“无副本”。将类/属性标记为“深度克隆”的另一个属性。等等。
    • @DavidL 这是一个很好的解决方案。然而,我对你的提议还有一个问题。 if 循环检查条件以查看 overrideValue 是否为空。但是,对于 int 类型属性,0 也可以是空属性。我尝试在 if 语句 if (overrideValue == null) || (int)value == 0) 中添加额外的检查,但是虽然 overrideValue 为 0,但在函数条件中未检测到。
    • @AdamWróbel 您可以为结构类型添加实例生成,我将其添加到我的答案中。但是请注意,这又是未优化的东西……您需要缓存结果。
    【解决方案3】:

    抱歉,c# 中没有内置的东西可以做到这一点。您的选择是:

    • 手动比较和分配每个字段
    • 使用反射自动比较和分配每个字段
    • 找到一个为您做这件事的图书馆

    对不起,我不知道有任何图书馆可以做到这一点。也许是 Automapper?

    【讨论】:

      猜你喜欢
      • 2019-10-04
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      • 2018-05-13
      • 2018-04-22
      • 2018-11-10
      • 1970-01-01
      • 2019-06-19
      相关资源
      最近更新 更多