Design Patterns - Unit of Work Pattern in C# using MongoDB (with transactions)

Before you look at the Unit of Work pattern example in C# using MongoDB, you can see my post about what is the Unit of Work pattern.

Unit of Work interface in C#

public interface IUnitOfWork
{
  IDisposable Session { get; }

  void AddOperation(Action operation);

  void CleanOperations();

  Task CommitChanges();
}

About this code snippet:

  • The property Session will be replaced by the MongoDB session object in the implementation of the Unit of Work.
  • The method AddOperation is used to add operations to the transaction.
  • The method CleanOperations clean all the operations (Before the execution of the transaction).
  • The method CommitChanges execute all operations in the transaction.

Implementation of the Unit of Work in C# using MongoDB

public sealed class UnitOfWork : IUnitOfWork
{
  private IClientSessionHandle session { get; }
  public IDisposable Session => this.session;

  private List<Action> _operations { get; set; }

  public UnitOfWork()
  {
    var mongoClient = new MongoClient("connectionString");
    this.session = mongoClient.StartSession();

    this._operations = new List<Action>();
  }

  public void AddOperation(Action operation)
  {
    this._operations.Add(operation);
  }

  public void CleanOperations()
  {
    this._operations.Clear();
  }

  public async Task CommitChanges()
  {
    this.session.StartTransaction();

    this._operations.ForEach(o =>
    {
        o.Invoke();
    });

    await this.session.CommitTransactionAsync();

    this.CleanOperations();
  }
}

About this code snippet:

  • The constructor starts a MongoDB session.
  • The property Session returns the MongoDB session object.

Repository interface in C#

public interface IProductRepository
{
  void Add(IProduct product);

  void Update(IProduct product);

  void Remove(ProductId id);
}

About this code snippet:

  • For this example, I am only including the methods that will go inside a transaction.
  • These actions are not asynchronous because they are not executed instantly, instead, they just add operations to the transaction.

Implementation of the Repository in C# using MongoDB

public sealed class ProductRepository : IProductRepository
{
  private readonly IMongoDatabase _database;
  private readonly IMongoCollection<ProductMongoEntity> _productsCollection;
  private readonly IUnitOfWork _unitOfWork;

  public ProductRepository(IUnitOfWork unitOfWork)
  {
    this._unitOfWork = unitOfWork;

    var mongoClient = new MongoClient("connectionString");
    this._database = mongoClient.GetDatabase("databaseName");
    this._productsCollection = this._database.GetCollection<ProductMongoEntity>("collectionName");
  }

  public void Add(IProduct product)
  {
    Action operation = () => this._productsCollection.InsertOne(this._unitOfWork.Session as IClientSessionHandle, product);
    this._unitOfWork.AddOperation(operation);
  }

  public void Update(IProduct product)
  {
    Action operation = () => this._productsCollection.ReplaceOne(this._unitOfWork.Session as IClientSessionHandle, x => x.Id == product.Id, product);
    this._unitOfWork.AddOperation(operation);
  }

  public void Remove(ProductId id)
  {
    Action operation = () => this._productsCollection.DeleteOne(this._unitOfWork.Session as IClientSessionHandle, x => x.Id == id.Id);
    this._unitOfWork.AddOperation(operation);
  }
}

About this code snippet:

  • The Unit of Work is injected into the repository because it is necessary to know the MongoDB session.
  • Each method of the Repository creates an action with the data of the operation to perform and adds it to the list of operations of the Unit of Work.