Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The record is created in database if transaction is aborted (MongoDB.Driver and .NETCore)

Did anyone tried using transactions in .NetCore? I tried it and I can not get it to work properly.

My setup:

  1. Mongo4 (3 node replica set)
  2. Visual Studio 2017
  3. MongoDB.Driver 2.7.0
  4. .Net Core 2.0. Console application

I am following the instructions: https://docs.mongodb.com/manual/core/transactions/

The problem is that new document is created in database every time (if I abort transaction, if I commit transaction,...)

I also tried using transactions directly on database and they work, I also tried it with NodeJS and they also work. Maybe there is a bug with driver, i do not know what I am doing wrong.

Code:

using System;
using MongoDB.Bson;
using MongoDB.Driver;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            var connString = "mongodb://user:password@localhost:27017";
            var client = new MongoClient(connString);

            using (var session = client.StartSession())
            {
                try
                {
                    RunTransactionWithRetry(UpdateEmployeeInfo, client, session);
                }
                catch (Exception exception)
                {
                    // do something with error
                    Console.WriteLine($"Non transient exception caught during transaction: ${exception.Message}.");
                }
            }

        }

        public static void RunTransactionWithRetry(Action<IMongoClient, IClientSessionHandle> txnFunc, IMongoClient client, IClientSessionHandle session)
        {
            while (true)
            {
                try
                {
                    txnFunc(client, session); // performs transaction
                    break;
                }
                catch (MongoException exception)
                {
                    // if transient error, retry the whole transaction
                    if (exception.HasErrorLabel("TransientTransactionError"))
                    {
                        Console.WriteLine("TransientTransactionError, retrying transaction.");
                        continue;
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }

        public static void CommitWithRetry(IClientSessionHandle session)
        {
            while (true)
            {
                try
                {
                    session.CommitTransaction();
                    Console.WriteLine("Transaction committed.");
                    break;
                }
                catch (MongoException exception)
                {
                    // can retry commit
                    if (exception.HasErrorLabel("UnknownTransactionCommitResult"))
                    {
                        Console.WriteLine("UnknwonTransactionCommiResult, retrying commit operation");
                        continue;
                    }
                    else
                    {
                        Console.WriteLine($"Error during commit: {exception.Message}.");
                        throw;
                    }
                }
            }
        }

        // updates two collections in a transaction
        public static void UpdateEmployeeInfo(IMongoClient client, IClientSessionHandle session)
        {
            var employeesCollection = client.GetDatabase("testdatabase").GetCollection<BsonDocument>("employees");
            var eventsCollection = client.GetDatabase("testdatabase").GetCollection<BsonDocument>("events");

            session.StartTransaction(new TransactionOptions(
                readConcern: ReadConcern.Snapshot,
                writeConcern: WriteConcern.WMajority));

            try
            {
                employeesCollection.UpdateOne(
                    Builders<BsonDocument>.Filter.Eq("employee", 3),
                    Builders<BsonDocument>.Update.Set("status", "Inactive"));
                eventsCollection.InsertOne(
                    new BsonDocument
                    {
                { "employee", 3 },
                { "status", new BsonDocument { { "new", "Inactive" }, { "old", "Active" } } }
                    });
            }
            catch (Exception exception)
            {
                Console.WriteLine($"Caught exception during transaction, aborting: {exception.Message}.");
                session.AbortTransaction();
                throw;
            }

            //I WANT TO ABORT TRANSACTION - BUT THE RECORD "employee:3...." IS STILL IN DATABASE "events"
            session.AbortTransaction();
        }

        public static void UpdateEmployeeInfoWithTransactionRetry(IMongoClient client)
        {
            // start a session
            using (var session = client.StartSession())
            {
                try
                {
                    RunTransactionWithRetry(UpdateEmployeeInfo, client, session);
                }
                catch (Exception exception)
                {
                    // do something with error
                    Console.WriteLine($"Non transient exception caught during transaction: ${exception.Message}.");
                }
            }
        }
    }
}
like image 735
Martin Belič Avatar asked Jul 12 '18 05:07

Martin Belič


People also ask

How is transaction is implemented in MongoDB database?

More often than not, transactions are instead used by external applications. To ensure that any transactions it runs remain atomic, consistent, isolated, and durable, the application must start a session. In MongoDB, a session is a database object managed by an application through the appropriate MongoDB driver.

Is MongoDB a transactional database?

Databases like MongoDB that support ACID transactions are known as transactional databases. Transactions allow developers to group database operations together in a way that they all succeed or all fail together.

What is the supported provided by MongoDB for transaction management?

In version 4.0, MongoDB supports multi-document transactions on replica sets. In version 4.2, MongoDB introduces distributed transactions, which adds support for multi-document transactions on sharded clusters and incorporates the existing support for multi-document transactions on replica sets.

Is transaction supported in MongoDB?

For situations that require atomicity of reads and writes to multiple documents (in a single or multiple collections), MongoDB supports multi-document transactions. With distributed transactions, transactions can be used across multiple operations, collections, databases, documents, and shards.


1 Answers

You need to pass in the session into the operations to include them into the transaction session. i.e. the InsertOne method accepts IClientSessionHandle as a first parameter.

Otherwise, the operations will act outside of the session as individual operations. Thus, the abort doesn't actually abort them.

Modifying your example:

var database = client.GetDatabase("testdatabase");
var employeesCollection = database.GetCollection<BsonDocument>("employees");
var eventsCollection = database.GetCollection<BsonDocument>("events");

session.StartTransaction(new TransactionOptions(
                             readConcern: ReadConcern.Snapshot,
                             writeConcern: WriteConcern.WMajority));

try
{
    employeesCollection.UpdateOne(
                session,
                Builders<BsonDocument>.Filter.Eq("employee", 3),
                Builders<BsonDocument>.Update.Set("status", "Inactive"));
    eventsCollection.InsertOne(
                session,
                new BsonDocument
                {
                    { "employee", 3 },
                    { "status", new BsonDocument { { "new", "Inactive" }, { "old", "Active" } } }
                });
}
catch (Exception exception)
{
     Console.WriteLine($"Caught exception during transaction, aborting: {exception.Message}.");
     session.AbortTransaction();
     throw;
}

// OR session.CommitTransaction(); 
session.AbortTransaction(); 

The example above was written utilising MongoDB .Net driver v2.7, and MongoDB 4.0.

Please note that MongoDB Multi-Document Transactions requires the collection namespace to exist.

like image 158
Wan Bachtiar Avatar answered Nov 10 '22 05:11

Wan Bachtiar