【问题标题】:Merging .net object graph合并.net对象图
【发布时间】:2010-06-16 09:08:54
【问题描述】:

有没有人遇到过需要将一个对象与另一个相同类型的对象合并的情况,从而合并完整的对象图。 例如如果我有一个人对象并且一个人对象有名字和另一个姓氏,则可以通过某种方式将两个对象合并为一个对象。

public class Person
{
  public Int32 Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

public class MyClass
{
   //both instances refer to the same person, probably coming from different sources
   Person obj1 = new Person(); obj1.Id=1; obj1.FirstName = "Tiju";
   Person obj2 = new Person(); ojb2.Id=1; obj2.LastName = "John";


   //some way of merging both the object
   obj1.MergeObject(obj2); //??
   //obj1.Id // = 1
   //obj1.FirstName // = "Tiju"
   //obj1.LastName // = "John"
}

我遇到过这种类型的需求,我写了一个扩展方法来做同样的事情。

    public static class ExtensionMethods
{
    private const string Key = "Id";



    public static IList MergeList(this IList source, IList target)
    {
        Dictionary itemData = new Dictionary();

        //fill the dictionary for existing list
        string temp = null;
        foreach (object item in source)
        {
            temp = GetKeyOfRecord(item);
            if (!String.IsNullOrEmpty(temp))
                itemData[temp] = item;
        }

        //if the same id exists, merge the object, otherwise add to the existing list.

        foreach (object item in target)
        {
            temp = GetKeyOfRecord(item);
            if (!String.IsNullOrEmpty(temp) && itemData.ContainsKey(temp))
                itemData[temp].MergeObject(item);
            else
                source.Add(item);
        }

        return source;
    }




    private static string GetKeyOfRecord(object o)
    {
        string keyValue = null;
        Type pointType = o.GetType();
        if (pointType != null)
            foreach (PropertyInfo propertyItem in pointType.GetProperties())
            {
                if (propertyItem.Name == Key)
                { keyValue = (string)propertyItem.GetValue(o, null); }
            }
        return keyValue;
    }




    public static object MergeObject(this object source, object target)
    {
        if (source != null && target != null)
        {
            Type typeSource = source.GetType();
            Type typeTarget = target.GetType();

            //if both types are same, try to merge
            if (typeSource != null && typeTarget != null && typeSource.FullName == typeTarget.FullName)
                if (typeSource.IsClass && !typeSource.Namespace.Equals("System", StringComparison.InvariantCulture))
                {
                    PropertyInfo[] propertyList = typeSource.GetProperties();

                    for (int index = 0; index < propertyList.Length; index++)
                    {
                        Type tempPropertySourceValueType = null;
                        object tempPropertySourceValue = null;
                        Type tempPropertyTargetValueType = null;
                        object tempPropertyTargetValue = null;

                        //get rid of indexers
                        if (propertyList[index].GetIndexParameters().Length == 0)
                        {
                            tempPropertySourceValue = propertyList[index].GetValue(source, null);
                            tempPropertyTargetValue = propertyList[index].GetValue(target, null);
                        }
                        if (tempPropertySourceValue != null)
                            tempPropertySourceValueType = tempPropertySourceValue.GetType();
                        if (tempPropertyTargetValue != null)
                            tempPropertyTargetValueType = tempPropertyTargetValue.GetType();



                        //if the property is a list
                        IList ilistSource = tempPropertySourceValue as IList;
                        IList ilistTarget = tempPropertyTargetValue as IList;
                        if (ilistSource != null || ilistTarget != null)
                        {
                            if (ilistSource != null)
                                ilistSource.MergeList(ilistTarget);
                            else
                                propertyList[index].SetValue(source, ilistTarget, null);
                        }

                        //if the property is a Dto
                        else if (tempPropertySourceValue != null || tempPropertyTargetValue != null)
                        {
                            if (tempPropertySourceValue != null)
                                tempPropertySourceValue.MergeObject(tempPropertyTargetValue);
                            else
                                propertyList[index].SetValue(source, tempPropertyTargetValue, null);
                        }
                    }
                }
        }
        return source;
    }


}

但是,这在源属性为空时有效,如果目标有它,它会将其复制到源。 当存在不一致时,仍然可以改进 IT 以合并,例如如果 FirstName="Tiju" 和 FirstName="John"

任何意见表示赞赏。

谢谢 TJ

【问题讨论】:

  • 我不明白你的问题是什么。您想要 cmets 如何改进您的代码吗?还是代码有问题?
  • 我的问题是,有没有其他人面临这样的要求,他们想出了什么想法。
  • 我创建了一个名为 MergeO 的库,它以可定制的通用方式完成此任务。 github.com/diskjunky/mergeo。它比您的实现更复杂,因为为了进行完整的深度合并,任何合并操作都可能需要根对象的上下文(例如,如果 PersonID &gt; 2 或其他深奥规则,则选择较新的一个)。

标签: .net reflection object merge graph


【解决方案1】:

您的代码看起来很糟糕。你确定你需要这样一个通用模型吗?您是否打算合并多个 Person 对象?如果是这样,合并要求是否完全相同?如果您需要合并其他类型,更改是它们必须以不同的方式合并。也许你应该选择没有反思的设计。这是另一个想法:

public interface IMergable<T>
{
    T MergeWith(T other);
}

public interface IEntity
{
    object EntityId { get; }
}

public class Person : IMergable<Person>, IEntity
{
    public int Id { get; set; }

    object IEntity.EntityId { get { return this.Id; } }

    public Person MergeWith(Person other)
    {
        var mergedperson = new Person();

        // Do merging here, and throw InvalidOperationException
        // when objects  can not be merged.

        return mergedPerson;
    }
}

你的扩展方法看起来像这样:

public static IEnumerable<T> MergeList<T>(this IEnumerable<T> left,
    IEnumerable<T> right)
    where T : IMergable<T>, IEntity
{
    return
       from leftEntity in left
       from rightEntity in right
       where leftEntity.EntityId.Equals(rightEntity.EntityId)
       select leftEntity.MergeWith(rightEntity);
}

【讨论】:

  • 不错的设计。 IEntity.EntityId 的类型是 object 而不是 int 有什么原因吗?我想当底层类型实际上是值类型时这将不起作用,因为leftEntity.EntityId == rightEntity.EntityId 是对始终评估为假的装箱值引用的比较。
  • @0xA3:好点。我解决了这个问题。我将该行更改为leftEntity.EntityId.Compare(rightEntity.EntityId)Entityid 是一个对象的原因,因为我不知道 OP 的实体 ID 是否总是 int 类型。如果是这样的话,代码当然会变得简单一些。
  • 您可以创建EntityId 类型为IEquatable&lt;T&gt; 并使用Equals() 方法。请注意,object 没有 Compare() 方法。
  • 非常锋利。我将其更改为Object.Equals。那应该工作:-)。还有关于IEquatable&lt;T&gt;的好消息。
【解决方案2】:

很好的方法史蒂文,

是的,我将合并相同类型的对象。 是的,我需要一个更通用的模型。

原因是我的架构不允许我继承关系,因为这些对象将是简单的 DTO,它们只是轻量级对象,只是发送到 UI clint 以进行绑定。所以这种关系是没有问题的。

客户端需要这个逻辑,确切地说,我的数据将来自不同的来源,比如一个来自 system1,另一个来自 system2,两者都将返回相同类型的对象,具有不同的值,我只是想要一种合并两个对象的方法,以便可以向客户端 UI 显示完整的信息。不会有任何从 UI 保存回系统的内容。代码应该是通用的,并且不应该包含任何类型特定的合并逻辑。 当一个对象中的一个属性为空而不在另一个对象中时,没有问题。当两者具有相同属性的不同值时,就会出现问题。

例如一位消息人士说有人住在美国,而另一位消息人士说有人住在法国。在这种情况下该怎么办。虽然这基本上是 BA 的一个问题,但也许每个来源的某种可信度指数,机制可能会起作用。

如果 Type 有其他 Dto 的列表,那么 MergeList 不仅会合并匹配的记录,还会将不匹配的记录添加到列表中。

任何关于使代码更好的 cmets :-)

【讨论】:

  • 这是否意味着即使在这些 DTO 上实现接口也是不可能的?用属性装饰它们怎么样,可以吗?或者有什么变化是不可能的? (它们是自动生成的吗?)我提供的MergeList 方法有点幼稚,它会加入。你也需要只在左边的记录,只在右边的记录?
  • 你是对的,没有接口,它们是自动生成的。我不能使用属性,因为这些 DTO 在客户端创建为代理(我的情况是 silverlight),因为这些类型是通过 WCF Web 服务公开的。如果我错了,请纠正我,我认为 .net 特定信息(如属性)不能传递给代理。我想要所有可能的记录,只要它们有不同的 Key
【解决方案3】:

不是直接的答案,但 匿名 类型值得考虑:

return new {
  FirstName = "Peter",
  LastName = "Pen"
};

欲了解更多信息,this article 很好地解释了该功能; msdnwikipedia 上还有更多内容。

总结一下:

  • 它们只能从对象继承,
  • 它们唯一的成员是私有字段,每个字段都有一个匹配的读/写属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-14
    • 1970-01-01
    • 2021-07-28
    • 2017-01-01
    • 2015-11-19
    相关资源
    最近更新 更多