Replace

ReplaceOne

You can replace a single document entirely by using the ReplaceOne method on an IMongoCollection<T>.

IMongoCollection<T>
    .ReplaceOne(FilterDefinition<T> filter, T document)

The sample replaces the first document with a new one.

ReplaceDocuments.cs
var collection = database
    .GetCollection<User>(Constants.UsersCollection);

// this is the new document - demo only
var newUser = RandomData.GenerateUsers(1).First();

// replace the first document in the collection
var replaceOneResult = await collection
    .ReplaceOneAsync(Builders<User>.Filter.Empty, newUser);

The identifier field _id on the new document must fulfill one of the following conditions:

  • If specified, must be equal to the current document's value

  • Not specified at all. MongoDB will create a new one automatically

⚠️ In case you do set a value for the identifier _id field and it's different than the current one, you will get the exception:After applying the update, the (immutable) field '_id' was found to have been altered to _id: <new-id>

One common scenario where you need to replace documents is when you want to move fields inside the documents. Consider that you have the following document.

{
        "_id" : ObjectId("5ea55f572109b8cadfc146e9"),
        "username" : "chsakell",
        "friends" : [
                "John",
                "Maria",
                "Catherine"
        ],
        "blocked" : [
                "Jake",
                "Helen"
        ]
}

The following sample moves friends and blocked top level fields in a new embedded document field named relationships.

ReplaceDocuments.cs
var socialAccountAfter = new SocialAccount
{
    Username = username,
    RelationShips = new RelationShips
    {
        Friends = friends,
        Blocked = blocked
    }
};

await socialNetworkCollection
    .ReplaceOneAsync(Builders<SocialAccount>.Filter
        .Eq(ac => ac.Username, username), socialAccountAfter);
        

When replacing a document make sure to be precise 🎯 on your filter, otherwise you might get a E11001 duplicate key on update error. For example, in the previous sample if the socialAccountAfter object already contained the _id value and there were more than one documents with username "chsakell" you would get the error. Why? Because MongoDB would try to set in more than one documents the same _id unique identifier value.

Upsert

If the filter in the ReplaceOne operation fails to match a document then nothing happens in the database. You can change this behavior by passing a replace options argument to the replace operation and setting upsert = true.

IMongoCollection<T>
    .ReplaceOne(FilterDefinition<T> filter, 
                T document, 
                ReplaceOptions options)

The sample tries to replace a user document that has company name "Microsoft Corp". If it finds a match then it will replace it with the microsoftCeo document but if it doesn't, it will insert it.

ReplaceDocuments.cs
var collection = database
    .GetCollection<User>(Constants.UsersCollection);

// this is the new document - demo only
var microsoftCeo = RandomData.GenerateUsers(1).First();
            microsoftCeo.FirstName = "Satya";
            microsoftCeo.LastName = "Nadella";
            microsoftCeo.Company.Name = "Microsoft";

// replace the first document in the collection
var addOrReplaceSatyaNadellaResult = await collection
    .ReplaceOneAsync<User>(u => u.Company.Name == "Microsoft Corp",
        microsoftCeo, new ReplaceOptions()
        {
            IsUpsert = true
        });

When no match found, the update result will be the following:

{
	"acknowledged" : true,
	"matchedCount" : 0, // no match found
	"modifiedCount" : 0, // so nothing modified
	"upsertedId" : ObjectId("5e99f20346296441706dfb4d")
}

πŸ¦‰ Since you set upsert = true a new document inserted with _id equal to the upsertedId

FindOneAndReplaceOne

IMongoCollection<T> contains a FindOneAndReplaceOne method that behaves exactly the same as the ReplaceOne except that the returned result is of type T instead of a ReplaceOneResult, in other words it returns the updated or upserted document itself. This can be quite convenient when you want to keep working with the new document after replacing it.

IMongoCollection<T>
    .FindOneReplaceOne(FilterDefinition<T> filter, 
                T document, 
                FindOneAndReplaceOptions options)

The sample replaces the first document with a new one and gets back the entire document.

ReplaceDocuments.cs
var collection = database
  .GetCollection<User>(Constants.UsersCollection);

var firstDbUser = await collection.
            Find(Builders<User>.Filter.Empty)
            .FirstOrDefaultAsync();
            
// this is the new document - demo only
var newUser = RandomData.GenerateUsers(1).First();
newUser.Id = firstDbUser.Id;
newUser.FirstName = "Chris";
newUser.LastName = "Sakellarios";
newUser.Website = "https://github.com/chsakell";

// replace the first document in the collection
var firstUser = await collection
.FindOneAndReplaceAsync(
    Builders<User>.Filter.Eq(u => u.Id, firstDbUser.Id), 
    newUser,
    new FindOneAndReplaceOptions<User> 
                {ReturnDocument = ReturnDocument.After});

FindOneAndReplaceOptions

When using the FindOneAndReplace method you have two options for the returned result:

  1. Return the updated document - you need to set ReturnDocument = ReturnDocument.After in the FindOneAndReplaceOptions

  2. Return the document before being updated - you need to set ReturnDocument = ReturnDocument.Before in the FindOneAndREplaceOptions or leave it as is since it's the default value

Last updated