Объединение двух похожих динамических объектов в 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.
3 ответа
Кажется, ваша проблема основана на интерфейсе IDictionary<>. Предполагается, что для каждого ключа есть только одно значение. Я считаю, что вы хотите скорее перейти на LINQ-иш 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] } ));
Конечно, это не сработает, но я написал это, чтобы увидеть, какие проблемы это может выявить. Первое - вы, вероятно, получите дубликаты ключей при Concat'ing. Тогда не все ключи находятся в 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, который является однозначным. Если вам нужно делать такие вещи и с многозначными входными данными, вы можете легко перевести словари в поиски и выполнить над ними операции:
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));
Обратите внимание, как финальные группы должны были быть переведены. В этом примере группы не IEnumerable<Value>
, но IEnumerable<IEnumerable<Value>>
потому что входные данные были разрешены быть многозначными, даже если они имели только 1 значение. Таким образом, это должно было быть сплющено, и это делает SelectMany. Это, в свою очередь, не должно было ничего проецировать, так как элемент уже был IEnumerable, так что =>s было достаточно.
Используя различные перегрузки GroupBy, ToLookup и ToDictionary, вы можете добиться многих полезных эффектов. Играй с перегрузками!
Если ваш динамический объект 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
чтобы получить свой результат.
Поскольку значение является 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"