Welcome! Spanish articles on LinkedIn. You can follow me on Twitter for news.
Table of Contents
MongoDB and C# introduction
🚀 MongoDB provides the ability to perform bulk insert, update, and delete operations.
In the MongoDB C# Driver,, we can use the BulkWriteAsync()
method that supports the following write operations:
- InsertOne
- UpdateOne
- UpdateMany
- DeleteOne
- DeleteMany
We are going to show how to use these methods with the MongoDB's C# driver.
I'll work through this sample in MongoDB Atlas, the global cloud database service offered by MongoDB.
You can create your cluster in minutes, completely free here
Let's begin
I create a user collection with the following fields.
- _id
- name
- createdAt
- isBlocked
YES! There is no password! It's not the scope of this post.
public class User
{
[BsonId] public ObjectId _id { get; set; }
public BsonString name { get; set; }
public BsonString email { get; set; }
public BsonDateTime createdAt { get; set; }
public BsonBoolean isBlocked { get; set; }
}
Insert operations
BulkWriteAsync method
This method is responsible for executing the bulk operations. BulkWriteAsync
takes a variable number (list) of WriteModel
instances.
We set our User model as a generic type parameter to the WriteModel
class.
var listWrites = new List<WriteModel<User>>();
Creating a fake user dataset
Then, we are going to create a fake dataset of 1000 new users and add this to the list of WriteModel type.
var totalNewUsers = 1000;
for (int i = 0; i < totalNewUsers; i++)
{
var newUser = new User
{
name = $"customName-{i}",
email = $"customEmail-{i}@domain{i}.com",
createdAt = DateTime.Now,
isBlocked = false
};
listWrites.Add(new InsertOneModel<User>(newUser));
}
Pay attention to the InsertOneModel
object. We are telling MongoDB that it's an insert operation.
Execute batch insert operation
Finally, we get the user collection and execute the bulk insert.
var userCollection = db.GetCollection<User>("users");
var resultWrites = await userCollection.BulkWriteAsync(listWrites);
Console.WriteLine($"OK?: {resultWrites.IsAcknowledged} - Inserted Count: {resultWrites.InsertedCount}");
By default, MongoDB executes the bulk insert with an ordered approach. That's means perform the operations serially. If an error occurs of one of the insert operation, MongoDB ends the bulk insert. If you want MongoDB to continue with the bulk insert, you need to specify the unordered approach passing a BulkWriteOptions
object:
var resultWrites = await userCollection.BulkWriteAsync(listWrites, new BulkWriteOptions
{
IsOrdered = false
});
Complete Code
public static async Task BulkInsertMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var listWrites = new List<WriteModel<User>>();
var totalNewUsers = 1000;
for (int i = 0; i < totalNewUsers; i++)
{
var newUser = new User
{
name = $"customName-{i}",
email = $"customEmail-{i}@domain{i}.com",
createdAt = DateTime.Now,
isBlocked = false
};
listWrites.Add(new InsertOneModel<User>(newUser));
}
var userCollection = db.GetCollection<User>("users");
var resultWrites = await userCollection.BulkWriteAsync(listWrites);
Console.WriteLine($"OK?: {resultWrites.IsAcknowledged} - Inserted Count: {resultWrites.InsertedCount}");
}
Update operations
After insert 1,000 new users, we are going to update some of them!
UpdateOne
This class (UpdateOneModel) provides us a way to update only one document that matches a specific condition.
We need to set up a filter definition (condition) and the update definition (what fields are going to update)
var filterDefinition = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
var updateDefinition = Builders<User>.Update.Set(p => p.isBlocked, true);
Then, add these definitions to our UpdateOneModel
instance.
listWrites.Add(new UpdateOneModel<User>(filterDefinition, updateDefinition));
Complete code for UpdateOne
public static async Task BulkUpdateOneMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var userCollection = db.GetCollection<User>("users");
var listWrites = new List<WriteModel<User>>();
var filterDefinition = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
var updateDefinition = Builders<User>.Update.Set(p => p.isBlocked, true);
listWrites.Add(new UpdateOneModel<User>(filterDefinition, updateDefinition));
await userCollection.BulkWriteAsync(listWrites);
}
UpdateMany
This class (UpdateManyModel) allows us to update multiple documents that match a specific condition.
As we did with UpdateOne, we need to set up a filter definition and the updated definition.
var filterDefinition = Builders<User>.Filter.Eq(p => p.isBlocked, false);
var updateDefinition = Builders<User>.Update.Set(p => p.isBlocked, true);
In this case, we are going to block all users that there aren't blocked yet.
Complete code for UpdateMany
public static async Task BulkUpdateManyMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var userCollection = db.GetCollection<User>("users");
var listWrites = new List<WriteModel<User>>();
var filterDefinition = Builders<User>.Filter.Eq(p => p.isBlocked, false);
var updateDefinition = Builders<User>.Update.Set(p => p.isBlocked, true);
listWrites.Add(new UpdateManyModel<User>(filterDefinition, updateDefinition));
await userCollection.BulkWriteAsync(listWrites);
}
UpdateOne and UpdateMany
As we saw, the implementations of these methods are the same. The main difference is that if the condition matches multiple documents, UpdateOne will update the first matching document only while UpdateMany updates all documents.
Delete operations
It's very similar to update operations, but in this case, we are going to delete some documents in our user collection.
DeleteOne
This class (DeleteOneModel) provides us a way to delete only one document that matches a specific condition
We need to set up a filter definition (condition)
var filterDefinition = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
In this case, we are going to delete the first document that matches an email equals customEmail-0@domain0.com.
Complete code for DeleteOne
public static async Task BulkDeleteOneMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var userCollection = db.GetCollection<User>("users");
var listWrites = new List<WriteModel<User>>();
var filterDefinition = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
listWrites.Add(new DeleteOneModel<User>(filterDefinition));
await userCollection.BulkWriteAsync(listWrites);
}
DeleteMany
This class (DeleteManyModel) allows us to delete multiple documents that match a specific condition.
As we did with DeleteOne, we need to set up a filter definition.
var filterDefinition = Builders<User>.Filter.Eq(p => p.isBlocked, true);
In this case, we are going to delete all users that there are blocked.
Complete code for DeleteMany
public static async Task BulkDeleteManyMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var userCollection = db.GetCollection<User>("users");
var listWrites = new List<WriteModel<User>>();
var filterDefinition = Builders<User>.Filter.Eq(p => p.isBlocked, true);
listWrites.Add(new DeleteManyModel<User>(filterDefinition));
await userCollection.BulkWriteAsync(listWrites);
}
DeleteOne and DeleteMany
As we said with UpdateOne and UpdateMany, the implementations of these methods are the same. The main difference is that if the condition matches multiple documents, DeleteOne will delete the first matching document only while DeleteMany deletes all documents.
Put it all together
MongoDB allows us to execute all of this together 🤩
The following sample does not make sense, but I want to show that it is possible!
We are going to group all previous operations in the same bulk write operations so that we will create 1,000 new users, then execute UpdateOne, UpdateMany, DeleteOne, and DeleteMany operations in the same bulk in order.
public static async Task BulkAllTogetherMongoDb()
{
IMongoDatabase db = _client.GetDatabase("sample_blog");
var listWrites = new List<WriteModel<User>>();
var totalNewUsers = 1000;
//InsertOne
for (int i = 0; i < totalNewUsers; i++)
{
var newUser = new User
{
name = $"customName-{i}",
email = $"customEmail-{i}@domain{i}.com",
createdAt = DateTime.Now,
isBlocked = false
};
listWrites.Add(new InsertOneModel<User>(newUser));
}
//UpdateOne
var filterDefinition = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
var updateDefinition = Builders<User>.Update.Set(p => p.isBlocked, true);
listWrites.Add(new UpdateOneModel<User>(filterDefinition, updateDefinition));
//UpdateMany
var filterDefinitionUpdateMany = Builders<User>.Filter.Eq(p => p.isBlocked, false);
var updateDefinitionUpdateMany = Builders<User>.Update.Set(p => p.isBlocked, true);
listWrites.Add(new UpdateManyModel<User>(filterDefinitionUpdateMany, updateDefinitionUpdateMany));
//DeleteOne
var filterDefinitionDeleteOne = Builders<User>.Filter.Eq(p => p.email, "customEmail-0@domain0.com");
listWrites.Add(new DeleteOneModel<User>(filterDefinitionDeleteOne));
//DeleteMany
var filterDefinitionDeleteMany = Builders<User>.Filter.Eq(p => p.isBlocked, true);
listWrites.Add(new DeleteManyModel<User>(filterDefinitionDeleteMany));
var userCollection = db.GetCollection<User>("users");
var resultWrites = await userCollection.BulkWriteAsync(listWrites);
Console.WriteLine($"OK?: {resultWrites.IsAcknowledged} - Inserted Count: {resultWrites.InsertedCount}");
Console.WriteLine($"Updated Count: {resultWrites.ModifiedCount}");
Console.WriteLine($"Deleted Count: {resultWrites.DeletedCount}");
}
Console output:
OK?: True - Inserted Count: 1000
Updated Count: 1000
Deleted Count: 1000
I hope you enjoyed the post!
Top comments (2)
Thanks, useful post! 👏
Would be great to extend it by error handling and transactions as you often need to roll back the whole bulk in case of any error and return the operation in the bulk which caused the error.
A transaction can be created by using a session:
Error handling can be done catching the MongoBulkWriteException:
Very useful article, thank you!