Overview
In this guide, you can learn how to use transactions in your EF Core Provider applications. A transaction wraps a series of write operations. If any operation in the transaction fails, the provider rolls back all changes in the transaction. This behavior helps ensure data consistency.
The EF Core Provider supports the following transaction modes:
Implicit transactions: The provider automatically wraps calls to the
SaveChanges()andSaveChangesAsync()methods in a transaction. See Configure Implicit Transactions to learn how to configure or disable this behavior.Explicit transactions: You call the
Database.BeginTransaction()method to manually begin a transaction, then commit or roll back the transaction by callingCommit()orRollback().
Sample Data
The examples in this guide use the planets collection from the
sample_guides database. The documents in this collection use the
following Planet class as a model:
public class Planet { public ObjectId _id { get; set; } public string name { get; set; } = null!; public int orderFromSun { get; set; } public bool hasRings { get; set; } }
This collection is from the sample datasets provided by Atlas. See the Quick Start guide to learn how to create a free MongoDB cluster and load this sample data.
Requirements and Limitations
Before you use transactions, note the following requirements and limitations:
Transactions require a MongoDB Server deployment that supports multi-document transactions, such as a replica set or sharded cluster. If your application tries to start a transaction on a standalone deployment, the provider throws a
NotSupportedException. To prevent this, setAutoTransactionBehavior.Neveras described in Configure Implicit Transactions.The EF Core Provider doesn't support .NET ambient transactions, such as those created by
System.Transactions.TransactionScope. If you try to use an ambient transaction, the provider throws an exception. Use implicit or explicit transactions instead.
Implicit Transactions
By default, the EF Core Provider automatically wraps SaveChanges()
and SaveChangesAsync() calls in a transaction when multiple root
entities are affected. A root entity is a top-level document in a
collection, as opposed to an owned or embedded type that is stored
inside another document.
The following example inserts two planets in a single SaveChanges()
call. Because this operation affects more than one root entity, the
provider automatically wraps it in a transaction. If either insert
fails, the provider rolls back both changes.
Select the Synchronous or Asynchronous tab to see the corresponding code.
dbContext.Planets.AddRange( new Planet { Name = "Mercury" }, new Planet { Name = "Venus" } ); // Both inserts succeed or both are rolled back dbContext.SaveChanges();
dbContext.Planets.AddRange( new Planet { Name = "Mercury" }, new Planet { Name = "Venus" } ); // Both inserts succeed or both are rolled back await dbContext.SaveChangesAsync();
Configure Implicit Transactions
The Database.AutoTransactionBehavior property on your DbContext object
controls when the provider uses implicit transactions. This property accepts a value
from the AutoTransactionBehavior enum, which contains the following
values:
Value | Description |
|---|---|
| The provider uses a transaction only when |
| The provider always uses a transaction, even for single-entity operations. |
| The provider never uses a transaction. |
You can set this property at any point before you call the SaveChanges() or
SaveChangesAsync() method, as shown in the following example.
Select the Synchronous or Asynchronous tab to see
the corresponding code.
dbContext.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; // This SaveChanges() call will not use a transaction dbContext.Planets.Add(new Planet { Name = "Mars" }); dbContext.SaveChanges();
dbContext.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; // This SaveChangesAsync() call will not use a transaction dbContext.Planets.Add(new Planet { Name = "Mars" }); await dbContext.SaveChangesAsync();
Explicit Transactions
You can use explicit transactions to group multiple
SaveChanges() calls and other operations into a single atomic
unit of work.
To start an explicit transaction, call the Database.BeginTransaction()
or Database.BeginTransactionAsync() method.
After you perform your operations, call Commit() or CommitAsync()
to complete the transaction.
The following example shows this pattern by wrapping multiple operations in a single transaction. If any operation throws an exception, the provider automatically rolls back all changes in the transaction. Select the Synchronous or Asynchronous tab to see the corresponding code.
using var transaction = dbContext.Database.BeginTransaction(); var planet = dbContext.Planets.First(p => p.Name == "Mercury"); planet.Name = "Mercury (Updated)"; dbContext.SaveChanges(); dbContext.Planets.Add(new Planet { Name = "Venus" }); dbContext.SaveChanges(); transaction.Commit();
await using var transaction = await dbContext.Database.BeginTransactionAsync(); var planet = await dbContext.Planets.FirstAsync(p => p.Name == "Mercury"); planet.Name = "Mercury (Updated)"; await dbContext.SaveChangesAsync(); dbContext.Planets.Add(new Planet { Name = "Venus" }); await dbContext.SaveChangesAsync(); await transaction.CommitAsync();
Tip
Manual Rollback
The examples on this page use a using declaration to automatically roll back
transactions and dispose of the transaction objects. To manually roll back a transaction,
call the Rollback() or RollbackAsync() method in a catch block.
Configure Explicit Transactions
You can pass a TransactionOptions object to the
BeginTransaction() or BeginTransactionAsync() method to configure the read
concern, write concern, read preference, and maximum commit time for the
transaction.
The TransactionOptions class has the following properties:
Property | Type | Description |
|---|---|---|
|
| Read concern for the first command in the transaction. The
default value is |
|
| Read preference for read operations in the transaction.
Transactions must read from the primary. The default value is |
|
| Write concern for the |
|
| Maximum amount of time to allow a single |
The following example starts a transaction with a ReadConcern of
Majority.
Select the Synchronous or Asynchronous tab to see
the corresponding code.
var options = new TransactionOptions( readConcern: new Optional<ReadConcern>(ReadConcern.Majority) ); using var transaction = dbContext.Database.BeginTransaction(options); var planet = dbContext.Planets.First(p => p.Name == "Mercury"); planet.Name = "Mercury (Updated)"; dbContext.SaveChanges(); transaction.Commit();
var options = new TransactionOptions( readConcern: new Optional<ReadConcern>(ReadConcern.Majority) ); await using var transaction = await dbContext.Database.BeginTransactionAsync(options); var planet = await dbContext.Planets.FirstAsync(p => p.Name == "Mercury"); planet.Name = "Mercury (Updated)"; await dbContext.SaveChangesAsync(); await transaction.CommitAsync();
To learn more about read concern, write concern, and read preference, see Read Concern, Write Concern, and Read Preference.
Transaction Limitations
The following limitations and cautions apply when you use transactions:
Disable automatic transactions only if your deployment doesn't support them. Disabling automatic transactions can lead to data inconsistencies and prevents you from using optimistic concurrency.
MongoDB Server doesn't support nested transactions. If you call
BeginTransaction()orBeginTransactionAsync()while a transaction is already active, the provider throws anInvalidOperationException.MongoDB Server doesn't support transaction savepoints. You must commit or roll back an entire transaction.
By default, MongoDB Server aborts any transaction that runs for more than 60 seconds, as controlled by the
transactionLifetimeLimitSecondsserver parameter. This limit applies to both implicit and explicit transactions. If your transaction exceeds this time limit, MongoDB Server aborts it and the provider throws an error. Avoid performing long-running operations, such as external API calls, within a transaction.If MongoDB Server experiences a network interruption or must perform a replica set election, it might return a
TransientTransactionErrorerror. In these cases, the EF Core Provider doesn't automatically retry the transaction. To learn more about transient transaction errors and retry strategies, see Transaction Error Handling.
Additional Information
To learn more about the concepts in this guide, see the following resources:
To learn more about the methods used in this guide, see the following .NET API documentation: