Did anyone tried using transactions in .NetCore? I tried it and I can not get it to work properly.
My setup:
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}.");
}
}
}
}
}
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With