Слияние двух похожих динамических объектов в C# с созданием коллекций по пути

У меня есть несколько динамических объектов, которые (в большинстве случаев) отличаются только несколькими значениями. Я хочу иметь возможность объединять эти объекты в один объект, а в случае конфликта (два значения не совпадают) я хочу, чтобы они хранились в коллекции или другом динамическом объекте (если возможно).

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

Есть ли способ сделать это простым и эффективным способом?

Я опубликую несколько примеров кода, чтобы дать вам небольшое представление о том, чего я пытаюсь достичь.

//returns a dynamic object from JSON (output from DataBase)
JsonReader reader = new JsonReader();
dynamic object = reader.Read(input);

//Creates a dictionairy with all the fieldnames and values.
IDictionary<string, dynamic> properties = (IDictionary<string, dynamic>)object;

//My goal is to merge multiple of these objects into one single object and
//creating collections when values are different from each other.

person Joey Dewd    schedule 15.08.2012    source источник
comment
Можете ли вы показать какой-либо код, иллюстрирующий то, что вы пробовали?   -  person psubsee2003    schedule 15.08.2012
comment
Конечно, я отредактирую свой пост, чтобы показать код   -  person Joey Dewd    schedule 15.08.2012


Ответы (3)


Ваша проблема, похоже, связана с интерфейсом IDictionary‹>. Предполагается, что для каждого ключа существует только одно значение. Я полагаю, что вы скорее хотите перейти к LINQ-ish IEnumerable<IGrouping<TKey,TValue>>, который представляет собой список коллекций значений с ключами.

LINQ создает такие объекты, когда вы выполняете вызовы .GroupBy или .ToLookup.
Тогда давайте поиграем:

using System.Linq;

Dictionary<string, dynamic> A = ...;
Dictionary<string, dynamic> B = ...;

// naiive atempt:
var lookup = 
    A.Keys.Concat(B.Keys)
        .ToDictionary(key => key, key => new dynamic[]{ A[key], B[key] } ));

Конечно, это не сработает, но я написал его, чтобы посмотреть, какие проблемы он выявит. Во-первых, вы, вероятно, получите дубликаты ключей при объединении. Тогда не все ключи находятся в A и B, поэтому индексаторы будут выбрасывать. Затем я случайно предположил, что исходные объекты были строкой-к-одному значению, и вы, вероятно, будете работать с уже столкнувшимися объектами. Затем используются только 2 объекта A/B, в то время как вы можете работать с несколькими.

IEnumerable<Dictionary<string, dynamic>> inputs = ....;

// btw. GropuBy returns a lookup, too :) a key -> all matches
var matched_by_keys = inputs.GroupBy(pair => pair.Key);

var final = matched_by_keys.ToDictionary(group => group.Key, group => group);

Посмотреть там. Я взял все словари и «просто соединил их в пары» в соответствии с их ключами. В результате у меня есть поиск, который связывает каждый существующий ключ с серией значений, которые ранее были назначены этому ключу. Полученный matched_by_keys является Enumerable, а не словарем, поэтому позже он был переведен в него. Посмотрите на параметры ToDictionary: сама группа является IEnumerable, просто нужно было указать ключ, и группа не изменилась.

Тем не менее, ввод работает только с однозначным IDictionary. Если вам нужно делать такие вещи и с многозначными входными данными, вы можете легко преобразовать IDictionaries в поиск и выполнять над ними операции:

IEnumerable<Dictionary<string, dynamic>> inputs1 = ....; // normal items
IEnumerable<ILookup<string, dynamic>> inputs2 = ....; // items that previously already collided

var allAsLookups =
    inputs1.ToLookup(pair => pair.Key, pair => pair.Value)
    .Concat( inputs2 );

// btw. GropuBy returns a lookup, too :) a key -> all matches
var matched_by_keys = lookups.GroupBy(lk => lk.Key);

var final1 = matched_by_keys.ToDictionary(group => group.Key, group => group.SelectMany(s=>s));

Обратите внимание, как должны были быть переведены последние группы. В этом примере group имеет значение не IEnumerable<Value>, а IEnumerable<IEnumerable<Value>>, потому что входным данным разрешено быть многозначным, даже если они имеют только одно значение. Итак, его нужно было сгладить, и это сделал SelectMany. Это, в свою очередь, не нужно было ничего проецировать, так как элемент уже был IEnumerable, поэтому s=>s было достаточно.

Используя различные перегрузки GroupBy, ToLookup и ToDictionary, вы можете добиться множества полезных эффектов. Играйте с перегрузками!

person quetzalcoatl    schedule 15.08.2012
comment
Очень полезная информация и хорошо объяснена! Мне нравится, как вы обращаетесь к версии, которая в данном случае не работает. Спасибо за информацию, я уверен, что смогу что-то придумать отсюда! :) - person Joey Dewd; 15.08.2012

Поскольку значение равно dynamic, вы действительно можете хранить либо значение, либо коллекцию по своему усмотрению.

Нам нужна какая-то коллекция словарей, в данном случае массив.

var props1 = new Dictionary<string, dynamic> 
    {
        {"uniq1", "uniq1"},
        {"same", "same1"},
    };
var props2 = new Dictionary<string, dynamic> 
    {
        {"uniq2", "uniq2"},
        {"same", "same2"},
    };
var props3 = new Dictionary<string, dynamic> 
    {
        {"uniq3", "uniq3"},
        {"same", "same3"},
    };

var props = new[] { props1, props2, props3 };

Затем возьмите первый и объедините его со следующим. Если ключ уже существует, проверьте, является ли он уже списком или нам нужно его создать. Если ключ не существует, просто добавьте его.

var merged = props.First();

foreach (var prop in props.Skip(1))
{
    foreach (var key in prop.Keys)
    {
        dynamic oldValue;
        if (merged.TryGetValue(key, out oldValue))
        {
            if (oldValue is IList<dynamic>)
                merged[key].Add(prop[key]);
            else
                merged[key] = new List<dynamic> { oldValue, prop[key] };
        }
        else
            merged.Add(key, prop[key]);
    }
}

merged будет установлено на:

merged
Count = 4
    [0]: {[uniq1, uniq1]}
    [1]: {[same, System.Collections.Generic.List`1[System.Object]]}
    [2]: {[uniq2, uniq2]}
    [3]: {[uniq3, uniq3]}
merged["same"]
{System.Collections.Generic.List<object>}
    [0]: "same1"
    [1]: "same2"
    [2]: "same3"
person Jonas Elfström    schedule 15.08.2012
comment
Большое Вам спасибо!. Хорошо объяснено с прекрасными примерами. Я постараюсь разобраться с этим, чистый код и простой для понимания :). - person Joey Dewd; 15.08.2012

Если ваш динамический объект Expando, вы можете использовать Linq, как показано ниже:

var objectList = new List<dynamic>() {...};

var result = objectList.SelectMany(obj => 
                           (IDictionary<string, dynamic>)obj.ToList())
            .GroupBy(pair => pair.Key)
            .ToDictionary(group => group.Key,
                          group => group.Select(pair => pair.Value));

Во-первых, используйте SelectMany, чтобы выбрать все KeyValuePair<string, dynamic> из динамических объектов, таким образом будет принято, что список имеет один и тот же ключ.

Затем сгруппируйте по ключу и используйте функцию ToDictionary, чтобы получить результат.

person cuongle    schedule 15.08.2012
comment
obj.ToList() нужен? - person Igor Pashchuk; 03.08.2015