Группировка позволяет сгруппировать документы в выборке по определенному критерию. Для группировки у объекта IMongoCollection
применяется метод Aggregate
, а у его результата - объекта IAggregateFluent вызывается метод Group().
Метод Group
в качестве параметра принимает BsonDocument, который описывает, как должна проводиться группировка. Например, пусть у нас будет следующая
коллекция employees со следующими документами:
{ "_id" : ObjectId("635ba2034b36410fce764516"), "Name" : "Tom", "Age" : 38, "Company" : { "Title" : "Microsoft" } } { "_id" : ObjectId("635ba2034b36410fce764517"), "Name" : "Bob", "Age" : 42, "Company" : { "Title" : "Google" } } { "_id" : ObjectId("635ba2034b36410fce764518"), "Name" : "Sam", "Age" : 25, "Company" : { "Title" : "Microsoft" } } { "_id" : ObjectId("635ba2034b36410fce764519"), "Name" : "Alice", "Age" : 33, "Company" : { "Title" : "Google" } } { "_id" : ObjectId("635ba2034b36410fce76451a"), "Name" : "Dan", "Age" : 33, "Company" : { "Title" : "Microsoft" } }
После "Company" представляет вложенный документ (условную компаниию, где работает человек), в котором свойство "Title" хранит название компании. Сгруппируем данные по этому полю (то есть сгруппируем данные по компании):
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection<BsonDocument>("employees"); var employees = await collection.Aggregate() .Group( new BsonDocument { { "_id", "$Company.Title" }, // группируем по имени компании { "count", new BsonDocument("$sum", 1) }, // получаем количество документов в группе }) .ToListAsync(); foreach (var employeeGroup in employees) Console.WriteLine(employeeGroup);
Итак, в метод Group()
передается документ BsonDocument, который устанавливает параметры группировки. При этом нам надо установить параметр
"_id" - ключ группировки. В данном случае для ключа выбрано поле Company.Title, при этом к названию поля добавляется знак
доллара: "$Company.Title". То есть каждая группа объектов будет иметь одно и то же значение поля "Company.Title".
{ "_id", "$Company.Title" }
И также можно установить дополнительные поля для группы. Так, здесь к группе добавляется поле count
, которое будет содержать количество
объектов в группе. Для подсчета количества применяется функция $sum
{ "count", new BsonDocument("$sum", 1) }
То есть в данном записи формально мы говорим, что значением поля "count" в выходном документе будет результат, который задается объектом new BsonDocument("$sum", 1)
.
А данный объект выполняет встроенную функцию mongodb - функцию "count", которая вычисляет количество документов в группе. Значение "1" указывает на множитель.
В результате мы получим следующий консольный вывод:
{ "_id" : "Google", "count" : 2 } { "_id" : "Microsoft", "count" : 3 }
count
- не единственный оператор, который мы можем использовать при группировке. Доступные операторы:
$sum
вычисляет сумму
$avg
вычисляет среднее значение
$first
получает значение поля из первого документа группы
$last
получает значение поля из последнего документа группы
$max
вычисляет максимальное значение
$min
вычисляет минимальное значение
$push
добавляет значение в массив
$addToSet
добавляет значение в набор
Например, по группам найдем среднее, максимальное и минимальное значение возраста (то есть значение поля "Age"):
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection<BsonDocument>("employees"); var employees = await collection.Aggregate() .Group( new BsonDocument { { "_id", "$Company.Title" }, { "minAge", new BsonDocument("$min", "$Age") }, { "maxAge", new BsonDocument("$max", "$Age") }, { "avgAge", new BsonDocument("$avg", "$Age") } }) .ToListAsync(); foreach (var employeeGroup in employees) Console.WriteLine(employeeGroup);
Консольный вывод:
{ "_id" : "Microsoft", "minAge" : 25, "maxAge" : 38, "avgAge" : 32.0 } { "_id" : "Google", "minAge" : 33, "maxAge" : 42, "avgAge" : 37.5 }
Другая задача - выведем для каждой группы значение "Name" первого и последнего документа группы:
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection<BsonDocument>("employees"); var employees = await collection.Aggregate() .Group( new BsonDocument { { "_id", "$Company.Title" }, { "first", new BsonDocument("$first", "$Name") }, { "last", new BsonDocument("$last", "$Name") } }) .ToListAsync(); foreach (var employeeGroup in employees) Console.WriteLine(employeeGroup);
Консольный вывод:
{ "_id" : "Microsoft", "first" : "Tom", "last" : "Dan" } { "_id" : "Google", "first" : "Bob", "last" : "Alice" }
Третья задача - добавм в каждую группу имена сотрудников соответствующей компании:
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection("employees"); var employees = await collection.Aggregate() .Group(new BsonDocument { { "_id", "$Company.Title" }, { "employees", new BsonDocument("$push", "$Name") } }) .ToListAsync(); foreach (var employeeGroup in employees) Console.WriteLine(employeeGroup);
Консольный вывод:
{ "_id" : "Google", "employees" : ["Bob", "Alice"] } { "_id" : "Microsoft", "employees" : ["Tom", "Sam", "Dan"] }
Вместо стандартного BsonDocument мы можем использовать свойства стандартных классов C#, которые описывают используемые данные:
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection<Employee>("employees"); var companies = await collection.Aggregate() .Group(emp => emp.Company!.Title, // группировка по свойству Company.Title g => new CompanyModel // из группы создаем объект CompanyModel { CompanyName = g.Key, Employees = g.Select(e=>e.Name).ToList() // выбираем в список имена сотрудников }) .ToListAsync(); foreach (var company in companies) { Console.WriteLine(company.CompanyName); foreach (var employeeName in company.Employees) Console.WriteLine(employeeName); Console.WriteLine(); } class CompanyModel { public string CompanyName { get; set; } = ""; public List<string> Employees { get; set; } = new(); } record Employee(ObjectId Id, string Name, int Age, Company? Company); record Company(string Title);
В данном случае коллекция типизируется типом Employee. В перегруженную версию метода Group передаем два делегата. Первый делегат выбирает из класса Employee свойство-признак, по которому будет идти группировка:
.Group(emp => emp.Company!.Title, // группировка по свойству Company.Title
Второй параметр-делегат в качестве параметра принимает созданную группу и возвращает сгенерированный на базе группы объект. В данном случае для представления объекта определен специальный класс CompanyModel (хотя можно было бы обойтись и анонимным объектом).
g => new CompanyModel // из группы создаем объект CompanyModel { CompanyName = g.Key, Employees = g.Select(e=>e.Name).ToList() // выбираем в список имена сотрудников })
Метод g.Select()
здесь фактически выполняет функцию операции "$push" - выбирает все имена сотрудников и формирует из них список.
Подобным образом можно задействовать и другие операторы группировки:
using MongoDB.Bson; using MongoDB.Driver; MongoClient client = new MongoClient("mongodb://localhost:27017"); var db = client.GetDatabase("test"); var collection = db.GetCollection<Employee>("employees"); var companies = await collection.Aggregate() .Group(emp => emp.Company!.Title, // группировка по свойству Company.Title g => new // из группы создаем анонимный объект { CompanyName = g.Key, Employees = g.Select(e=>e.Name).ToList(), // выбираем в список имена сотрудников SumAge = g.Sum(e=>e.Age), MinAge = g.Min(e=>e.Age), MaxAge = g.Max(e=>e.Age), AvgAge = g.Average(e=>e.Age), First = g.First().Name, Lass = g.Last().Name }) .ToListAsync(); foreach (var company in companies) { Console.WriteLine(company.ToJson()); } record Employee(ObjectId Id, string Name, int Age, Company? Company); record Company(string Title);