I'm implementing a DAL abstraction layer on top of the C# Mongo DB driver using the Repository + Unit of Work pattern. My current design is that every unit of work instance opens (and closes) a new Mongo DB session. The problem is that Mongo DB only allows a 1:1 ratio between a session and a transaction, so multiple units of work under the same .NET transaction will not be possible.
The current implementation is:
public class MongoUnitOfWork
{
private IClientSessionHandle _sessionHandle;
public MongoUnitOfWork(MongoClient mongoClient)
{
_sessionHandle = mongoClient.StartSession();
}
public void Dispose()
{
if (_sessionHandle != null)
{
// Must commit transaction, since the session is closing
if (Transaction.Current != null)
_sessionHandle.CommitTransaction();
_sessionHandle.Dispose();
}
}
}
And because of this, the following code won't work. The first batch of data will be committed ahead of time:
using (var transactionScope = new TransactionScope())
{
using (var unitOfWork = CreateUnitOfWork())
{
//... insert items
unitOfWork.SaveChanges();
} // Mongo DB unit of work implementation will commit the changes when disposed
// Do other things
using (var unitOfWork = CreateUnitOfWork())
{
//... insert some more items
unitOfWork.SaveChanges();
}
transactionScope.Complete();
}
Obviously the immediate answer would be to bring all of the changes into one unit of work, but it's not always possible, and also this leaks the Mongo DB limitation.
I thought about session pooling, so that multiple units of work will use the same session, and commit/rollback when the transient transaction completes/aborts.
What other solutions are possible?
Clarification:
The question here is specifically regarding Unit-Of-Work implementation over MongoDB using the MongoDB 4.0 (or later) built-in transaction support.
I never used MongoDB; do not know anything about it. I am only answering in terms of TransactionScope
; so not sure if this will help you.
Please refer the Magic Of TransactionScope. IMO, there are three factors you should look for:
Connection to the database should be opened inside the TransactionScope
.
So remember, the connection must be opened inside the TransactionScope block for it to enlist in the ambient transaction automatically. If the connection was opened before that, it will not participate in the transaction.
Not sure but it looks that you can manually enlist the connection opened outside the scope using connection.EnlistTransaction(Transaction.Current)
.
Looking at your comment and the edit, this is not an issue.
All operations should run on same thread.
The ambient transaction provided by the
TransactionScope
is a thread-static (TLS) variable. It can be accessed with staticTransaction.Current
property. Here is theTransactionScope
code at referencesource.microsoft.com. ThreadStatic ContextData, contains theCurrentTransaction
.
and
Remember that Transaction.Current is a thread static variable. If your code is executing in a multi-threaded environments, you may need to take some precautions. The connections that need to participate in ambient transactions must be opened on the same thread that creates the TransactionScope that is managing that ambient transaction.
So, all the operations should run on same thread.
Play with TransactionScopeOption
(pass it to constructor of TransactionScope
) values as per your need.
Upon instantiating a
TransactionScope
by thenew
statement, the transaction manager determines which transaction to participate in. Once determined, the scope always participates in that transaction. The decision is based on two factors: whether an ambient transaction is present and the value of theTransactionScopeOption
parameter in the constructor.
I am not sure what your code expected to do. You may play with this enum values.
As you mentioned in the comment, you are using async/await
.
Lastly, if you are using async/await inside the TransactionScope block, you should know that it does not work well with TransactionScope and you might want to look into new TransactionScope constructor in .NET Framework 4.5.1 that accepts a TransactionScopeAsyncFlowOption. TransactionScopeAsyncFlowOption.Enabled option, which is not the default, allows TransactionScope to play well with asynchronous continuations.
For MongoDB, see if this helps you.
The MongoDB driver isn't aware of ambient TransactionScopes. You would need to enlist with them manually, or use JohnKnoop.MongoRepository which does this for you: https://github.com/johnknoop/MongoRepository#transactions
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