MongoDB Transactions in .NET Applications

This post gives you a general idea on Database Transactions, defines what transactions in MongoDB are and explains how to run your MongoDB as a replicaset. You will also learn how to use MongoDB Transactions in a .NET application.

Ram Hemasri

This is a tutorial post on how to get started with MongoDB Transactions in .NET applications. I am assuming that you are familiar with maintaining a MongoDB instance and run commands in Mongo shell. You will learn how to set your MongoDB instance in replication mode and use transactions in a .NET application while using MongoDB database.

Transactions

In Database Management Systems, transactions are a sequence of one or more data manipulation/query operations executed as a single unit of work. The transaction will commit the changes when successful, or rollback the data to prior state as it was at the before the transaction began.

Transactions typically follows the sequences as shown below.

The main purpose of the transactions is to maintain data integrity. When the database operations are not part of a transaction there is always a risk of leaving data in an inconsistent or unreliable state. To minimise that risk, the database should always follow the ACID principles to protect data integrity from hardware failures or power loss.

ACID can be described as follows:

Atomicity is the key principle in a transaction. If one of the task fails then the whole transaction fails and data is rolled back to prior state.

Consistency ensures that the database always remains in a consistent state and that any saved chages will not violate data integrity.

Isolation ensures multiple transactions can be executed independently without affecting each other.

Durability guarantees that all the changes during a successful transaction are permanent even during system restarts.

A classic example of Transactions: a user wants to transfer money from their checking account to their savings. To do this, first we have to debit 10$ from the checking account and credit 10$ to savings account. If database throws an error when crediting the savings account, the user will be left 10$ less in the Checking account. This is where transactions and atomicity comes to play, since All-or-none operations will be performed. The users account changes are saved in the database only when all the operations are successful, otherwise the changes will be "rolled back" by not saving any of the changes.

MongoDB Transactions

MongoDB v4.0 has started supporting transactions across multiple documents, collections, databases and shards. Previous versions of MongoDB supported atomic operations only on a single document and also wanted us to use embedded document structure instead of normalizing across multiple collections, similar to relational databases. This single document sometimes has its own limitations. A 2-phase commit system was used in versions prior to V4.0 to perform operations across multiple documents/collections/databases.

Prerequisites

MongoDB transactions requires a replica set cannot work on a single instance and will throw the error message Transaction numbers are only allowed on a replica set member or mongos.

In MongoDB, transactions are always associated with a session. You will need a session to start a transaction. When a session abruptly ends the transaction will be aborted and data changes will be rolled back.

Demo

Here is a simple C# example. To use this example, first create a new Console application and install MongoDB.Driver nugget.

using MongoDB.Driver;
using System;
namespace MongoTransactions
{
public class Product
{
[MongoDB.Bson.Serialization.Attributes.BsonId]
public MongoDB.Bson.ObjectId Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
//1. Create a new mongo client with local connection string
var mongoClient = new MongoClient("mongodb://localhost:27017");
//2. Get a database named catalog
var db = mongoClient.GetDatabase("catalog");
var productFilter = new ListCollectionNamesOptions {
Filter = Builders<MongoDB.Bson.BsonDocument>.Filter.Eq("name", nameof(Product))
};
//3. Create a collection named Product.
//Transactions can only be performed on existing collection
if (!db.ListCollectionNames(productFilter).Any()) {
db.CreateCollection(nameof(Product));
}
//4. Get product collection
var products = db.GetCollection<Product>(nameof(Product));
//5. Start a new session. Transactions requires a session.
using (var session = mongoClient.StartSession())
{
try
{
//6. Start a transaction from the session.
session.StartTransaction();
//7. Insert a new document with the session started in step 5.
products.InsertOne(session, new Product { Name = "MySql" });
var filter = Builders<Product>.Filter.Eq(p => p.Name, "Oracle");
//8. Delete a document with the same session.
var result = products.DeleteOne(session, filter);
//9. The deleted count will be 0 as we haven't inserted a product named Oracle.
if (result.DeletedCount == 0)
{
//10. By throwing an exception we can abort transaction from catch block.
throw new Exception("No document found");
}
// This step will be skipped as we had thrown an exception in step 12.
session.CommitTransaction();
}
catch (Exception)
{
//11. By aborting the transaction, changes in Product collection will not persist.
session.AbortTransaction();
}
}
}
}
}

Summary

In this post we saw how easy it is to use MongoDB transactions in a .NET application. MongoDB Multi-document transactions makes it easier to maintain data integrity by enforcing all-or-nothing execution. As you have seen in the sample code, MongoDB will save the changes only after committing transaction. If a runtime error occurs the application will abort the transaction in catch block.