MongoDB версии 2.2 была выпущена в конце августа, и самым большим изменением стало добавление Aggregation Framework. Раньше для агрегации требовалось использовать отображение / уменьшение, которое в MongoDB работает не так хорошо, в основном из -за однопоточного исполнения на основе Javascript . Инфраструктура агрегирования отходит от Javascript и реализована на C ++ с целью повысить производительность аналитики и отчетности до 80 процентов по сравнению с использованием MapReduce.
Цель этого поста — показать примеры запуска MongoDB Aggregation Framework с официальными драйверами MongoDB C #.
Структура агрегации и Linq
Хотя текущая версия драйверов MongoDB C # (1.6) поддерживает Linq, эта поддержка не распространяется на структуру агрегации. Вполне вероятно, что Linq-поддержка будет добавлена позже, и в исходном коде драйвера уже есть некоторые намеки на это. Но на этом этапе выполнение агрегатов требует использования BsonDocument-объектов.
Структура агрегации и GUID
Если вы используете GUID в своих документах, структура агрегирования не работает. Это связано с тем, что по умолчанию GUID хранятся в двоичном формате, и агрегаты не будут работать с документами, которые содержат двоичные данные. Решение состоит в том, чтобы хранить GUID в виде строк. Вы можете заставить драйверы C # сделать это преобразование автоматически, настроив отображение. Учитывая, что ваш класс C # имеет Id-свойство, определенное как GUID, следующий код говорит драйверу сериализовать GUID в виде строки:
BsonClassMap.RegisterClassMap<MyClass>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(c => c.Id)
.SetRepresentation(
BsonType.String);
});
Пример данных
В этих примерах используются следующие документы:
> db.examples.find ()
{«_id»: «1», «User»: «Tom», «Country»: «Finland», «Count»: 1}
{«_id»: «2», «User «:» Tom «,» Country «:» Finland «,» Count «: 3}
{» _id «:» 3 «,» User «:» Tom «,» Country «:» Finland «,» Count «: 2 }
{«_id»: «4», «Пользователь»: «Мэри», «Страна»: «Швеция», «Количество»: 1}
{«_id»: «5», «Пользователь»: «Мэри», » Страна «:» Швеция «,» Граф «: 7}
Пример 1. Базовое использование Aggregation Framework
Этот пример показывает, как структура агрегации может быть выполнена через C #. Мы не собираемся проводить какие-либо вычисления с данными, мы просто собираемся отфильтровать их по пользователю.
Для запуска агрегатов вы можете использовать MongoDatabase.RunCommand –method или вспомогательный MongoCollection.Aggregate. Мы собираемся использовать последний:
var coll = localDb.GetCollection("examples");
...
coll.Aggregate(pipeline);
Самая сложная часть при работе с Aggregation Framework через C # — это создание конвейера. Трубопровод аналогична концепции к трубопроводу в PowerShell . Каждая операция в конвейере вносит изменения в данные: операции могут, например, фильтровать, группировать и проецировать данные. В C # конвейер является коллекцией объекта BsonDocument. Каждый документ представляет одну операцию.
В нашем первом примере нам нужно сделать только одну операцию: $ match. Этот оператор отфильтрует данные документы. Следующий BsonDocument является конвейерной операцией, которая отфильтровывает все документы, для которых в поле User не установлено значение «Tom».
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"User", "Tom"}
}
}
};
Для выполнения этой операции мы добавляем ее в массив и передаем массив методу MongoCollection.Aggregate:
var pipeline = new[] { match };
var result = coll.Aggregate(pipeline);
MongoCollection.Aggregate-метод возвращает объект AggregateResult. Свойство ResultDocuments (IEnumarable <BsonDocument>) содержит документы, являющиеся выходными данными агрегации. Чтобы проверить, сколько было результатов, мы можем получить Count:
var result = coll.Aggregate(pipeline);
Console.WriteLine(result.ResultDocuments.Count());
Полученные документы являются объектами BsonDocument. Если у вас есть C # -класс, который представляет документы, вы можете привести результаты:
var matchingExamples = result.ResultDocuments
.Select(BsonSerializer.Deserialize<ExampleData>)
.ToList();
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1}", example.User, example.Count);
Console.WriteLine(message);
}
Другой альтернативой является использование динамического типа C #. Следующий метод расширения использует JSON.net для преобразования BsonDocument в динамический:
public static class MongoExtensions
{
public static dynamic ToDynamic(this BsonDocument doc)
{
var json = doc.ToJson();
dynamic obj = JToken.Parse(json);
return obj;
}
}
Вот способ преобразовать все полученные документы в динамические объекты:
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
Пример 2. Несколько фильтров и операторов сравнения
В этом примере данные фильтруются по следующим критериям:
- Пользователь: Tom
- Количество:> = 2
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"User", "Tom"},
{"Count", new BsonDocument
{
{
"$gte", 2
}
}}
}
}
};
Выполнение этой операции идентично первому примеру:
var pipeline = new[] { match };
var result = coll.Aggregate(pipeline);
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
Также результат, как и ожидалось:
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1}", example.User, example.Count);
Console.WriteLine(message);
}
Пример 3: несколько операций
В наших первых двух примерах конвейер был максимально прост: он содержал только одну операцию. В этом примере будут отфильтрованы данные по тем же критериям, что и во втором примере, но на этот раз с использованием двух операций $ match:
- Пользователь: Tom
- Количество:> = 2
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"User", "Tom"}
}
}
};
var match2 = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"Count", new BsonDocument
{
{
"$gte", 2
}
}}
}
}
};
var pipeline = new[] { match, match2 };
Результат остается прежним:
Первая операция «сопоставить» берет все документы из коллекции примеров и удаляет каждый документ, который не соответствует критериям User = Tom. Выходные данные этой операции (3 документа) затем перемещаются ко второй операции «match2» конвейера. Эта операция видит только те 3 документа, а не оригинальную коллекцию. Операция отфильтровывает эти документы на основе своих критериев и перемещает результат (2 документа) вперед. На этом наш трубопровод заканчивается, и это тоже наш результат.
Пример 4: Группа и сумма
До сих пор мы использовали структуру агрегирования, чтобы просто отфильтровать данные. Истинная сила фреймворка заключается в его способности выполнять расчеты по документам. В этом примере показано, как можно рассчитать количество документов в коллекции, сгруппированной по пользователю. Это делается с помощью оператора $ group :
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{
"MyUser","$User"
}
}
},
{
"Count", new BsonDocument
{
{
"$sum", 1
}
}
}
}
}
};
Группирования ключа (в нашем случае пользователя поле) определяются с _id. В приведенном выше примере указано, что ключ группировки имеет одно поле («MyUser»), и значение этого поля берется из пользовательского поля документа ($ User). В операции $ group другие поля являются агрегатными функциями . В этом примере определяется поле «Количество» и добавляется 1 к нему для каждого документа, который соответствует ключу группы (_id).
var pipeline = new[] { group };
var result = coll.Aggregate(pipeline);
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1}", example._id.MyUser, example.Count);
Console.WriteLine(message);
}
Обратите внимание на формат, в котором выводятся результаты: имя пользователя доступно через _id.MyUser-property.
Пример 5: Группировка и сумма по полю
Этот пример аналогичен примеру 4. Но вместо того, чтобы вычислять количество документов, мы вычисляем сумму полей Count пользователем:
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{
"MyUser","$User"
}
}
},
{
"Count", new BsonDocument
{
{
"$sum", "$Count"
}
}
}
}
}
};
Единственное изменение состоит в том, что вместо добавления 1 мы добавляем значение из поля Count («$ Count»).
Пример 6: Прогнозы
В этом примере показано, как оператор $ project может использоваться для изменения формата вывода. Группировка в примере 5 работает хорошо, но для доступа к имени пользователя нам необходимо указать свойство _id.MyUser. Давайте изменим это так, чтобы имя пользователя было доступно напрямую через UserName-property:
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{
"MyUser","$User"
}
}
},
{
"Count", new BsonDocument
{
{
"$sum", "$Count"
}
}
}
}
}
};
var project = new BsonDocument
{
{
"$project",
new BsonDocument
{
{"_id", 0},
{"UserName","$_id.MyUser"},
{"Count", 1},
}
}
};
var pipeline = new[] { group, project };
Код удаляет _id –property из вывода. Он добавляет свойство UserName, значение которого доступно из поля _id.MyUser. Операции проецирования также утверждают, что значение Count должно оставаться как есть.
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1}", example.UserName, example.Count);
Console.WriteLine(message);
}
Пример 7: Группировка с несколькими полями в ключах
Для этого примера мы добавляем новую строку в нашу коллекцию документов, оставляя нам следующее:
{«_id»: «1», «Пользователь»: «Том», «Страна»: «Финляндия», «Количество»: 1}
{«_id»: «2», «Пользователь»: «Том», «Страна» «:» Finland «,» Count «: 3}
{» _id «:» 3 «,» User «:» Tom «,» Country «:» Finland «,» Count «: 2}
{» _id «:» 4 «,» Пользователь «:» Мэри «,» Страна «:» Швеция «,» Количество «: 1}
{» _id «:» 5 «,» Пользователь «:» Мэри «,» Страна «:» Швеция «,» Count «: 7}
{» _id «:» 6 «,» User «:» Tom «,» Country «:»Англия «,» Граф «: 3}
В этом примере показано, как можно сгруппировать данные, используя несколько полей в ключе группировки:
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{ "MyUser","$User" },
{ "Country","$Country" },
}
},
{
"Count", new BsonDocument
{
{ "$sum", "$Count" }
}
}
}
}
};
var project = new BsonDocument
{
{
"$project",
new BsonDocument
{
{"_id", 0},
{"UserName","$_id.MyUser"},
{"Country", "$_id.Country"},
{"Count", 1},
}
}
};
var pipeline = new[] { group, project };
var result = coll.Aggregate(pipeline);
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1} - {2}", example.UserName, example.Country, example.Count);
Console.WriteLine(message);
}
Пример 8: Матч, группа и проект
Этот пример показывает, как вы можете комбинировать множество различных конвейерных операций. Данные сначала фильтруются ( $ match ) по User = Tom, затем группируются по стране (« $ group ») и, наконец, выходные данные форматируются в читаемый формат ( $ project ).
Совпадение:
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"User", "Tom"}
}
}
};
Группа:
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{ "Country","$Country" },
}
},
{
"Count", new BsonDocument
{
{ "$sum", "$Count" }
}
}
}
}
};
Проект:
var project = new BsonDocument
{
{
"$project",
new BsonDocument
{
{"_id", 0},
{"Country", "$_id.Country"},
{"Count", 1},
}
}
};
Результат:
var pipeline = new[] { match, group, project };
var result = coll.Aggregate(pipeline);
var matchingExamples = result.ResultDocuments
.Select(x => x.ToDynamic())
.ToList();
foreach (var example in matchingExamples)
{
var message = string.Format("{0} - {1}", example.Country, example.Count);
Console.WriteLine(message);
}
Больше
В платформе агрегации MongoDB есть много других интересных операторов, таких как $ unwind и $ sort . Использование этих операторов идентично тем, которые мы использовали выше, поэтому должна быть возможность скопировать и вставить один из примеров и использовать его в качестве основы для этих других операций.