Working with MongoDB Transactions with C# and the .NET Framework

Facebook ShareLinkedin ShareReddit ShareTwitter Share

C# applications connected to a MongoDB database use the MongoDB .NET driver. To add the .NET driver to your Visual Studio Application, in the NuGet Package Manager, search for “MongoDB”.


Make sure you choose the latest version (>=2.7) of the driver, and press Install.

Prior to MongoDB version 4.0, MongoDB was transactionally consistent at the document level. These existing atomic single-document operations provide the transaction semantics to meet the data integrity needs of the majority of applications. This is because the flexibility of the document model allows developers to easily embed related data for an entity as arrays and sub-documents within a single, rich document. That said, there are some cases where splitting the content into two or more collections would be appropriate, and for these cases, multi-document ACID transactions makes it easier than ever for developers to address the full spectrum of use cases with MongoDB. For a deeper discussion on MongoDB document model design, including how to represent one-to-many and many-to-many relationships, check out https://docs.mongodb.com/manual/core/data-model-design/.

In the following code we will create a Product object and perform a MongoDB transaction that will insert some sample data into MongoDB then update the prices for all products by 10%.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Bson.Serialization.Attributes;

namespace MongoDBTransaction { class Program { public class Product { [BsonId] public ObjectId Id { get; set; } [BsonElement("SKU")] public int SKU { get; set; } [BsonElement("Description")] public string Description { get; set; } [BsonElement("Price")] public Double Price { get; set; } }

    const string MongoDBConnectionString = "<<YOUR MONGODB CONNECTION STRING>>";

    static async Task Main(string[] args)
    {
       if (!await UpdateProducts()) { Environment.Exit(0); }
        Console.WriteLine("Finished updating the product collection");
        Console.ReadKey();
    }
    static async Task<bool> UpdateProducts()
    {
        //Create client connection to our MongoDB database
        var client = new MongoClient(MongoDBConnectionString);

        //Create a session object that is used when leveraging transactions
        var session = client.StartSession();

        //Create the collection object that represents the "products" collection
        var products = session.Client.GetDatabase("MongoDBStore").GetCollection<Product>("products");

        //Clean up the collection if there is data in there
        products.Database.DropCollection("products");

        //Create some sample data
    var TV = new Product { Description = "Television", SKU = 4001, Price = 2000 };
    var Book = new Product { Description = "A funny book", SKU = 43221, Price = 19.99 };
    var DogBowl = new Product { Description = "Bowl for Fido", SKU = 123, Price = 40.00 };

        //Begin transaction
        session.StartTransaction();

        try
        {
            //Insert the sample data 
            await products.InsertOneAsync(TV);
            await products.InsertOneAsync(Book);
            await products.InsertOneAsync(DogBowl);

            var filter = new FilterDefinitionBuilder<Product>().Empty;
            var results = await products.Find<Product>(filter).ToListAsync();
            Console.WriteLine("Original Prices:\n");
            foreach (Product d in results)
            {
                Console.WriteLine(String.Format("Product Name: {0}\tPrice: {1:0.00}", d.Description, d.Price));
            }

            //Increase all the prices by 10% for all products
            var update = new UpdateDefinitionBuilder<Product>().Mul<Double>(r=>r.Price,1.1);
            await products.UpdateManyAsync(filter, update); //,options);

            //Made it here without error? Let's commit the transaction
            session.CommitTransaction();

            //Let's print the new results to the console
            Console.WriteLine("\n\nNew Prices (10% increase):\n");
            results = await products.Find<Product>(filter).ToListAsync();
            foreach (Product d in results)
            {
                Console.WriteLine(String.Format("Product Name: {0}\tPrice: {1:0.00}", d.Description, d.Price));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error writing to MongoDB: " + e.Message);
            session.AbortTransaction();
      return false;
        }
        return true;
    }
  }

}

Source Code available on Gist. Successful execution yields the following:


Key points:

  • You don’t have to match class properties to JSON objects – just define a class object and insert it directly into the database. There is no need for an Object Relational Mapper (ORM) layer.
  • MongoDB transactions use snapshot isolation meaning only the client involved in the transactional session sees any changes until such time as the transaction is committed.
  • The MongoDB .NET Driver makes it easy to leverage transactions and leverage LINQ based syntax for queries.

Additional information about using C# and the .NET driver can be found in the C# and .NET MongoDB Driver documentation.