Skip to content

Conversation

@damieng
Copy link
Member

@damieng damieng commented Dec 11, 2025

This allows ExecuteInTransaction to be used on ExecutionStrategy as on exception it simply disposes the transaction by way of a using rather than explicitly rolling back.

@damieng damieng marked this pull request as ready for review December 11, 2025 15:17
@damieng damieng requested a review from a team as a code owner December 11, 2025 15:17
@damieng damieng requested review from adelinowona and Copilot and removed request for adelinowona December 11, 2025 15:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modifies the transaction disposal behavior to automatically rollback active transactions instead of throwing an exception. This enables the use of ExecuteInTransaction on ExecutionStrategy which disposes transactions via using statements on exceptions rather than explicitly rolling back.

Key Changes:

  • Modified MongoTransaction.Dispose() and DisposeAsync() to automatically rollback active transactions instead of throwing exceptions
  • Removed tests that verified the old behavior (disposing without commit/rollback throws)
  • Updated tests to reflect new behavior where dispose without commit/rollback silently rolls back
  • Added new tests for ExecuteInTransaction integration with execution strategies

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
src/MongoDB.EntityFrameworkCore/Storage/MongoTransaction.cs Changed disposal methods to automatically rollback active transactions; removed unused import and helper method
tests/MongoDB.EntityFrameworkCore.FunctionalTests/Storage/TransactionTests.cs Removed old tests expecting exceptions on dispose, updated tests to reflect silent rollback behavior, added new tests for ExecuteInTransaction integration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@damieng damieng requested a review from ajcvickers December 12, 2025 10:29
break;

default:
throw new InvalidOperationException($"Can not Dispose MongoTransaction {TransactionId} because it is {_transactionState}.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we throw instead of rolling back for the "Committing" and "RollingBack" states? Also, do we want to throw by default if a new state is introduced? If we're responsible for the lifetime of the transaction, and it is disposed while in anything other than a final state (Failed, Committed, RolledBack), then I think we should attempt to roll back, since the transaction is going away.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Committing and RollingBack are in-progress states. They should never get here.

Even if we do I don't think we can roll-back mid-commit and I'm sure we can't roll back mid roll back.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you point me to where these states are defined so I can read up on it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an enum at the start of this file.

We set it to Committing at the start of the Commit methods, and RollingBack at the start of the RollBack methods.

If it's either of these states when disposing it basically has to either be either a bug in our code or a concurrency issue where they are trying to use the non-thread-safe MongoTransaction across multiple threads and one is doing a dispose while the other is performing a commit or rollback.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I spent some more time looking at all the code here, rather than just the PR, and I now understand these are just states we have, not the server or driver. Given that, as you say, this is not thread-safe, then Committing and RollingBack are not observable outside the class for any thread-safe correct code. So, first, why even have them? Second, if we detect that we are in a state that can only be reached by executing in a thread-unsafe manner, then we should explicitly say that in the exception message so that people know this is why the code threw--like what System.Collections.Generic does when you try to modify a collection while it is in use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants