Слияние графа объекта .net - PullRequest
2 голосов
/ 16 июня 2010

кто-нибудь сталкивался с любым сценарием, в котором вам нужно было объединить один объект с другим объектом того же типа, объединяя полный граф объектов. например, Если у меня есть объект person, а один объект person имеет имя, а другое - фамилию, это может привести к объединению обоих объектов в один объект.

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;
    }


}

Тем не менее, это работает, когда свойство source имеет значение null, если у target оно есть, оно скопирует его в источник. ИТ все еще можно улучшить, чтобы объединить, когда есть несоответствия, например if FirstName = "Tiju" и FirstName = "John"

Любые комментарии приветствуются.

Спасибо TJ

Ответы [ 3 ]

0 голосов
/ 17 июня 2010

Хороший подход, Стивен,

Да, я буду объединять объекты одного типа. Да, мне нужна более общая модель.

Причина в том, что моя архитектура не позволяет мне наследовать отношения, так как эти объекты будут простыми DTO, которые являются только легкими объектами, только что отправленными в клинт пользовательского интерфейса для привязки. так что такого рода отношения исключены.

Эта логика потребуется на стороне клиента и, если быть точным, мои данные будут поступать из разных источников, скажем, один из system1, а другой из system2, оба будут возвращать объект одного типа с разными значениями, я просто хочу способ объединить оба объекта, чтобы полная информация могла быть показана клиентскому интерфейсу. Там не будет никаких сохранений из пользовательского интерфейса обратно в систему. Код должен быть универсальным и не должен содержать логику слияния для конкретного типа. Нет проблем, когда одно свойство является нулевым в одном объекте, а не в другом. проблема возникает, когда оба будут иметь разные значения одинаковых свойств.

например один источник говорит, что человек живет в США, а другой говорит, что человек живет во Франции. что делать в таком сценарии. хотя это в основном вопрос к БА, но, возможно, какой-то индекс достоверности для каждого источника, механизм может работать.

Если у Type есть список других Dto, то MergeList не только объединит соответствующие записи, но и добавит несовпадающие записи в список.

любые комментарии по улучшению кода: -)

0 голосов
/ 17 июня 2010

Не прямой ответ, но анонимный классы типы, о которых стоит подумать:

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

Для получения дополнительной информации эта статья хорошо объясняет эту функцию; о msdn и wikipedia .

Подводя итог:

  • Они могут наследовать только от объекта,
  • их единственными членами являются частные поля, каждое из которых имеет соответствующее свойство чтения / записи.
0 голосов
/ 16 июня 2010

Ваш код выглядит очень отвратительно.Вы уверены, что вам нужна такая общая модель?Вы планируете объединить больше, чем объекты 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;
    }
}

Метод расширения Yur будет выглядеть примерно так:

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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...