Хобрук: Ваш путь к мастерству в программировании

Объединение графа объектов .net

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

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


}

Однако это работает, когда свойство источника имеет значение null, если оно есть у цели, оно скопирует его в источник. ИТ все еще можно улучшить, чтобы объединить, когда есть несоответствия, например. если Имя = "Тиху" и Имя = "Джон"

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

Спасибо, ТиДжей.


  • Я не понимаю, в чем твой вопрос. Хотите комментариев, как можно улучшить ваш код? Или проблема в коде? 16.06.2010
  • у меня вопрос, кто-нибудь еще сталкивался с такими требованиями, какие идеи придумали. 17.06.2010
  • Я создал библиотеку под названием MergeO, которая делает именно это и настраиваемым универсальным способом. github.com/diskjunky/mergeo. Это сложнее, чем ваша реализация, так как для полного глубокого слияния любой операции слияния может потребоваться контекст корневых объектов (например, выбрать более новый, если PersonID > 2, или другое эзотерическое правило). 06.11.2019

Ответы:


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);
}
16.06.2010
  • Хороший дизайн. Есть ли причина, по которой IEntity.EntityId имеет тип object, а не int? Я предполагаю, что это не будет работать, когда базовый тип на самом деле является типом значения, потому что leftEntity.EntityId == rightEntity.EntityId — это сравнение ссылок на значения в штучной упаковке, которые всегда оцениваются как ложные. 16.06.2010
  • @ 0xA3: хорошая мысль. Я исправил это. Я изменил эту строку на leftEntity.EntityId.Compare(rightEntity.EntityId). Причина, по которой Entityid является объектом, заключается в том, что я не знал, всегда ли идентификаторы сущностей OP имеют тип int. Если это так, код, конечно, станет немного проще. 16.06.2010
  • Вы можете сделать EntityId из типа IEquatable<T> и использовать метод Equals(). Обратите внимание, что object не имеет метода Compare(). 16.06.2010
  • Очень острый. Я изменил его на Object.Equals. Должно сработать :-). Также хорошее замечание о IEquatable<T>. 16.06.2010

  • 2

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

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

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

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

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

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

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

    17.06.2010
  • Означает ли это, что даже реализация интерфейсов на этих DTO не может быть и речи? Как насчет того, чтобы украсить их атрибутом, это нормально? Или ни о каких изменениях не может быть и речи? (Они генерируются автоматически?) Предоставленный мной метод MergeList немного наивен, он выполняет соединение. Вам тоже нужны записи только в лево, а записи только в право? 17.06.2010
  • вы правы, интерфейсов нет, и они генерируются автоматически. Я не могу использовать атрибуты, так как эти DTO создаются как прокси-серверы на клиенте (мой случай silverlight), поскольку эти типы выставляются через веб-службу WCF. Поправьте меня, если я ошибаюсь, я думаю, что конкретная информация .net, такая как атрибуты, не может быть передана прокси. Мне нужны все возможные записи, если они имеют разные ключи 17.06.2010

  • 3

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

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

    Дополнительную информацию можно найти в этой статье, где подробно объясняется эта функция; есть еще на msdn и википедия.

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

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

    React on Rails
    Основное приложение Reverb - это всеми любимый монолит Rails. Он отлично обслуживает наш API и уровень просмотра трафика. По мере роста мы добавляли больше интерактивных элементов..

    Что такое гибкие методологии разработки программного обеспечения
    Что представляют собой гибкие методологии разработки программного обеспечения в 2023 году Agile-методологии разработки программного обеспечения заключаются в следующем: И. Введение A...

    Ториго  — революция в игре Го
    Наш следующий вызов против ИИ и для ИИ. Сможет ли он победить людей в обновленной игре Го? Обратите внимание, что в следующей статье AI означает искусственный интеллект, а Goban  —..

    Простое развертывание моделей с помощью Mlflow — Упаковка классификатора обзоров продуктов NLP от HuggingFace
    Как сохранить свои модели машинного обучения в формате с открытым исходным кодом с помощью MLFlow, чтобы позже получить возможность легкого развертывания. Сегодня модели упаковки имеют несколько..

    Математика и интуиция - Часть 1
    У каждой математической формулы есть доказательство. Часто эти доказательства слишком сложно понять, поскольку многие из них основаны на индукции, некоторые - на очень сложных наблюдениях, а..

    Раскрытие возможностей НЛП: часть речевой маркировки и ее проблемы
    В сфере обработки естественного языка (NLP) маркировка частей речи (POS) выступает в качестве фундаментального метода, позволяющего компьютерам понимать и анализировать человеческий язык на..

    Под поверхностью: раскрытие деталей системы с помощью инструментов Linux CLI
    Чем больше вы изучаете Linux и продвигаетесь вперед, тем больше вам нужно проверять информацию о вашей системе. Эта информация может касаться аппаратного обеспечения, такого как процессор,..