Редактирование документов

Последнее обновление: 29.10.2022

Для редактирования документов коллекции применяется ряд методов:

  • ReplaceOne()/ReplaceOneAsync(): полностью заменяет один документ другим

  • UpdateOne()/UpdateOneAsync(): обновляет один документ/p>

  • UpdateMany()/UpdateManyAsync(): обновляет набор документов

  • FindOneAndReplace()/FindOneAndReplaceAsync(): если находит документ, который соответствует фильтру, то заменяет его и возвращает заменный документ

  • ReplaceOne()/ReplaceOneAsync(): полностью заменяет один документ другим

Они имеют разные версии, принимают разное количество параметров. Но все они как минимум принимают два параметра:

  • Фильтр, который позволяет отфильтровать документы для обновления

  • новый документ, который заменяет старый или описывает принципы изменения старого

При обновлении надо учитывать, что мы не можем изменить поле _id.

ReplaceOneAsync

Метод ReplaceOneAsync() позволяет полностью заменить старый документ новым. Допустим, у нас есть коллекция с именем "users", которая хранит следующие документы:

{ "_id" : ObjectId("63596dc76348749cac779373"), "Name" : "Tom", "Age" : 38, "Languages" : ["english", "german", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779374"), "Name" : "Bob", "Age" : 42, "Languages" : ["english"] }
{ "_id" : ObjectId("63596dc76348749cac779375"), "Name" : "Sam", "Age" : 25, "Languages" : ["english", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779376"), "Name" : "Alice", "Age" : 33 }
{ "_id" : ObjectId("63596dc76348749cac779377"), "Name" : "Tom", "Age" : 33, "Languages" : ["english"] }

Например:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("users");

// определяем фильтр - документ, где Name = Tom и Age = 33
var filter = new BsonDocument { { "Name", "Tom" }, { "Age", 33 } };
// определяем документ, на который будет заменять
var newDocument = new BsonDocument { { "Name", "Tomas" }, { "Age", 34 } };
// выполняем замену
var result = await collection.ReplaceOneAsync(filter, newDocument);

Console.WriteLine($"Найдено по соответствию: {result.MatchedCount}; обновлено: {result.ModifiedCount}");

// проверяем - выводи документы после обновления
var users = await collection.Find("{}").ToListAsync();
foreach (var user in users) Console.WriteLine(user);

В методе ReplaceOneAsync() первый параметр описывает критерии выборки. В данном случе фильтр хранится в переменной filter, которая представляет все документы, где "Name"="Tom" и "Age" = 34:

var filter = new BsonDocument { { "Name", "Tom" }, { "Age", 33 } };

Второй параметр описывает документ, который вставляется на место старого. Здесь его представляет переменная newDocument

var newDocument = new BsonDocument { { "Name", "Tomas" }, { "Age", 34 } };

Если документов, которые соответствуют фильтру, отсутствуют в выборке, то соответственно и замен не будет. Если фильтру соответствуют несколько документов, то будет заменен только первый из них.

Метод ReplaceOneAsync() возвращает объект ReplaceOneResult, с помощью которого мы можем получить информацию об обновлении, в частности, получить количество документов, которые соответствуют критерию, и количество обновленных документов.

Консольный вывод программы:

Найдено по соответствию: 1; обновлено: 1
{ "_id" : ObjectId("63596dc76348749cac779373"), "Name" : "Tom", "Age" : 38, "Languages" : ["english", "german", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779374"), "Name" : "Bob", "Age" : 42, "Languages" : ["english"] }
{ "_id" : ObjectId("63596dc76348749cac779375"), "Name" : "Sam", "Age" : 25, "Languages" : ["english", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779376"), "Name" : "Alice", "Age" : 33 }
{ "_id" : ObjectId("63596dc76348749cac779377"), "Name" : "Tomas", "Age" : 34 }

Обратите внимание, что начальный документ содержал массив "Languages", в новом же документе такой массив не определен, то есть произошла полная замена документа.

Вставка документа

С помощью дополнительного параметра ReplaceOptions метода ReplaceOneAsync() можно управлять параметрами обновления. new ReplaceOptions { IsUpsert = true } мы можем указать обязательность вставки документа в коллекцию, даже если не найдено соответствий по критерию:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection("users");

// определяем фильтр - документ, где Name = Alex и Age = 33
var filter = new BsonDocument { { "Name", "Alex" }, { "Age", 33 } }; 
// определяем документ, на который будет заменять
var newDocument = new BsonDocument { { "Name", "Alexander" }, { "Age", 34 } };
// выполняем замену, если документ не найден, то вставку
var result = await collection.ReplaceOneAsync(filter, newDocument, new ReplaceOptions { IsUpsert=true });

Console.WriteLine($"Matched: {result.MatchedCount}; Modified: {result.ModifiedCount}; UpsertedId: {result.UpsertedId}");

var users = await collection.Find("{}").ToListAsync();
foreach (var user in users) Console.WriteLine(user);

В данном случае меняем документ, который попадает под фильтр new BsonDocument { { "Name", "Alex" }, { "Age", 33 } } на документ new BsonDocument { { "Name", "Alexander" }, { "Age", 34 } }. С помощью свойства result.UpsertedId можно получить id добавленного оъекта.

Консольный вывод программы:

Matched: 0; Modified: 0; UpsertedId: 635bcb6a9abf25799fc1c4c3
{ "_id" : ObjectId("63596dc76348749cac779373"), "Name" : "Tom", "Age" : 38, "Languages" : ["english", "german", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779374"), "Name" : "Bob", "Age" : 42, "Languages" : ["english"] }
{ "_id" : ObjectId("63596dc76348749cac779375"), "Name" : "Sam", "Age" : 25, "Languages" : ["english", "spanish"] }
{ "_id" : ObjectId("63596dc76348749cac779376"), "Name" : "Alice", "Age" : 33 }
{ "_id" : ObjectId("63596dc76348749cac779377"), "Name" : "Tomas", "Age" : 34 }
{ "_id" : ObjectId("635bcb6a9abf25799fc1c4c3"), "Name" : "Alexander", "Age" : 34 }

UpdateOneAsync

Если ReplaceOne/ReplaceOneAsync полностью заменяют весь документу, то методы UpdateOne().UpdateOneAsync() позволяют обновить только отдельные поля документа:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("users");

// определяем фильтр - документ, где Name = Tom
var filter = new BsonDocument("Name", "Tom"); 
// определяем параметры обновления - обновляем только поле Age
var updateSettings = new BsonDocument("$set", new BsonDocument("Age", 39));
// выполняем обновление
var result = await collection.UpdateOneAsync(filter, updateSettings);

Console.WriteLine($"Matched: {result.MatchedCount}; Modified: {result.ModifiedCount}");

var users = await collection.Find("{}").ToListAsync();
foreach (var user in users) Console.WriteLine(user);

Первый параметр метода UpdateOneAsync устанавливает фильтр документа - в данном случае ищем документ, где Name = "Tom":

var filter = new BsonDocument("Name", "Tom"); 

Второй параметр метода UpdateOneAsync задает принципы обновления. В частности здесь используется оператор $set, который задает набор полей и их обновленных значений. То есть здесь обновляем только поле Age - устанавливаем ему значение 39:

var updateSettings = new BsonDocument("$set", new BsonDocument("Age", 39));

Результат метода UpdateOneAsync (как и в случае с ReplaceOneAsync) позволяет узнать количество соответствий и обновлений.

С помощью $set можно обновить значения сразу нескольких полей. Например, присвоим полю Name значение "Tom", а полю Age число 33:

var filter = new BsonDocument("Name", "Tomas");
var updateSettings = new BsonDocument("$set", new BsonDocument { { "Name", "Tom" }, { "Age", 33 } });
var result = await collection.UpdateOneAsync(filter, updateSettings);

Для обновления мы можем использовать все те же самые операторы консоли mongodb, которые рассматривались в теме Обновление данных в MongoDB. Оператор $set

Например, применим оператор $inc, который увеличивает числовое значение на несколько единиц:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Bob"), 
    new BsonDocument("$inc", new BsonDocument("Age", 2)));

Здесь увеличиваем у документа с Name="Bob" значение поля "Age" на 2.

Удаление поля

Для удаления отдельного поля используется оператор $unset. Например, удалим поле "Age":

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Alexander"),
    new BsonDocument("$unset", new BsonDocument("Age", 1)));

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

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$unset", new BsonDocument { { "Age", 1 }, { "Languages", 1 } }));

Обновление массивов. Оператор $push

Оператор $push позволяет добавить еще одно значение в массив. Если поле-массив отсутствует, то оно автоматически создается. Например, добавим значение в поле Languages:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$push", new BsonDocument { { "Languages", "english" }}));

Используя оператор $each, можно добавить сразу несколько значений:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$push", 
            new BsonDocument("Languages", 
                new BsonDocument("$each", new BsonArray {"french", "german"}))));

Оператор $addToSet

Оператор $addToSet подобно оператору $push добавляет объекты в массив. Отличие состоит в том, что $addToSet добавляет данные, если их еще нет в массиве ( при добавлении через $push данные дублируются, если добавляются элементы, которые уже есть в массиве):

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$addToSet", new BsonDocument("Languages", "italian")));

Удаление элемента из массива

Оператор $pop позволяет удалять элемент из массива:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$pop", new BsonDocument("Languages", 1)));

Указывая для поля Languages значение 1, мы удаляем первый элемент с конца. Чтобы удалить первый элемент сначала массива, надо передать отрицательное значение:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$pop", new BsonDocument("Languages", -1)));

Несколько иное действие предполагает оператор $pull. Он удаляет каждое вхождение элемента в массив. Например, через оператор $push мы можем добавить одно и то же значение в массив несколько раз. И теперь с помощью $pull удалим его:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$pull", new BsonDocument("Languages", "german")));

А если мы хотим удалить не одно значение, а сразу несколько, тогда мы можем применить оператор $pullAll:

var result = await collection.UpdateOneAsync(
    new BsonDocument("Name", "Sam"),
    new BsonDocument("$pullAll", new BsonDocument("Languages", new BsonArray { "english", "french" })));

UpdateManyAsync

Для обновления множества документов применяются методы UpdateMany()/UpdateManyAsync(), использование которых аналогично UpdateOne/UpdateOneAsync. Например, увеличим значение поля Age на 1 в документах, где Name = "Tom":

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<BsonDocument>("users");

// Увеличим Age на 1 в документах, где Name = Tom
var result = await collection.UpdateManyAsync(
    new BsonDocument("Name", "Tom"),
    new BsonDocument("$inc", new BsonDocument("Age", 1)));

Console.WriteLine($"Matched: {result.MatchedCount}; Modified: {result.ModifiedCount}");

var users = await collection.Find("{}").ToListAsync();
foreach (var user in users) Console.WriteLine(user);

UpdateDefinitionBuilder

Для обновления документов вместо BsonDocument можно использовать объект UpdateDefinitionBuilder, у которого определяется ряд методов, соответствющих консольным операторам mongodb:

  • Set: изменяет значение поля

  • AddToSet: добавляет новые элементы в поле документа, которое представляет массив. Например, добавляем в массив Languages новую строку: Builders<BsonDocument>.Update.AddToSet("Languages", "spanish")

  • Inc: инкрементирует значение числового свойства на указанное число единиц. Например, увеличим значение свойства Age на две единицы: Builders<BsonDocument>.Update.Inc("Age", 2)

  • Mul: умножает значение числового свойства на указанное число

  • Push: добавляет новые элементы для ключа, который представляет массив. Например: Builders<BsonDocument>.Update.Push("Languages", "french")

  • Pull: удаляет определенный элемент из массива. Например: Builders<BsonDocument>.Update.Pull("Languages", "french")

  • Unset: удаляет ключ и его значение. Например: Builders<BsonDocument>.Update.Unset("Age")

  • PopFirst: выбирает первый элемент для свойства, которое представляет массив

  • PopLast: выбирает последний элемент для свойства, которое представляет массив

  • Rename: переименовывает название ключа в документе. Например, переименуем поле Age в Year: Builders<BsonDocument>.Update.Rename("Age", "Year");.

Например, применим метод Set(). Этот метод в качестве парамета принимает свойство, которое надо изменить, и его обновленное значение. Например, установим свойство Age равным 30 у всех объектов, у которых Name="Tom".

// параметр фильтрации 
var filter = Builders<BsonDocument>.Filter.Eq("Name", "Tom");
// параметр обновления
var update = Builders<BsonDocument>.Update.Set("Age", 30);
var result = await collection.UpdateManyAsync(filter, update);

При работе с методом Set также надо учитывать, что если такого поля еще нет в документе в базе данных, например, оно просто ранее не было установлено, то данное поле добавляется в документ.

C помощью вспомогательного метода CurrentDate(), который имеется в UpdateDefinitionBuilder, можно установить у документа время последнего изменения:

var filter = Builders<BsonDocument>.Filter.Eq("Name", "Tom");
var update = Builders<BsonDocument>.Update.Set("Age", 33).CurrentDate("LastModified");
var result = await collection.UpdateManyAsync(filter, update);

В данном случае к документу добавляется поле LastModified, которое хранит время обновления.

FindOneAndReplaceAsync

Метод FindOneAndReplaceAsync() ищет документ, который соответствует критерию. И если документ найден, полностью заменяет его другим и возвращает его в качестве результата. Если документ не найден, ничего не делает и просто возвращает null:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test");
var collection = db.GetCollection<BsonDocument>("users");

// заменяем документ с Name="Tom" на {{"Name", "Tomas"}, { "Age", 22}}
var result = await collection.FindOneAndReplaceAsync(new BsonDocument("Name", "Tom"), new(){{"Name", "Tomas"}, { "Age", 22}});
if (result == null)
    Console.WriteLine("Документ не найдены");
else
    Console.WriteLine($"Заменен документ: {result}");

В данном случае документ с "Name"="Tom" заменяется на документ {{"Name", "Tomas"}, { "Age", 22}}. В качестве результата возвращается старый документ, который был заменен. Если нам надо возвращать новый документ, на который произошла замена, то необходимо в метод в качестве третьего параметра передать объект FindOneAndReplaceOptions со свойством ReturnDocument = ReturnDocument.After.

var result = await collection.FindOneAndReplaceAsync(
    new BsonDocument("Name", "Tomas"),          // фильтр
    new BsonDocument { { "Name", "Tom" }, { "Age", 38 } },      // на какой документ заменяем
    new FindOneAndReplaceOptions<BsonDocument>() { ReturnDocument = ReturnDocument.After });

FindOneAndUpdateAsync

Метод FindOneAndUpdateAsync() ищет документ, который соответствует критерию. И если документ найден, полностью обновляет его в соответствии с параметрами обновления. В качестве результата по умолчанию возвращаются поля документа до обновления другим. Если документ, который соответствует фильтру, не найден, то метод просто возвращает null:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test");
var collection = db.GetCollection<BsonDocument>("users");

var result = await collection.FindOneAndUpdateAsync(
    new BsonDocument("Name", "Tom"),          // фильтр
    new BsonDocument("$set", new BsonDocument("Age", 39)));

if (result == null)
    Console.WriteLine("Документ не найдены");
else
    Console.WriteLine($"Обновленный документ: {result}"); 

В данном случае в документе с "Name"="Tom" для поля "Age" устанавливается значение 39. В качестве результата возвращается документ со старыми значениями до обновления. Если нам надо возвращать документ со значениями после обновления, то необходимо в метод в качестве третьего параметра передать объект FindOneAndUpdateOptions со свойством ReturnDocument = ReturnDocument.After.

var result = await collection.FindOneAndUpdateAsync(
    new BsonDocument("Name", "Tom"),          // фильтр
    new BsonDocument("$set", new BsonDocument("Age", 22)),
   new FindOneAndUpdateOptions<BsonDocument>() { ReturnDocument = ReturnDocument.After });

Обновление и Poco-классы

Обновление стандартных Poco-типов в C#, например, объектов Person, будет аналогично:

using MongoDB.Bson;
using MongoDB.Driver;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
var collection = db.GetCollection<Person>("users");

// параметр фильтрации 
var filter = Builders<Person>.Filter.Eq(p=>p.Name, "Tom");
// параметр обновления
var update = Builders<Person>.Update.Set(p=>p.Age, 34);
// обновляем
var result = await collection.UpdateManyAsync(filter, update);

Console.WriteLine($"найдено по соответствию: {result.MatchedCount}; обновлено: {result.ModifiedCount}");

var users = await collection.Find("{}").ToListAsync();
foreach (var user in users) Console.WriteLine(user.ToJson());

class Person
{
    public ObjectId Id { get; set; }
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public List<string>? Languages { get; set; }
    public DateTime? LastModified { get; set; }
}

При использовании типов POCO, например, Person, в методах UpdateDefinitionBuilder мы можем применять лямбда-выражения: Update.Set(x=>x.Age, 34)

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850