Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs Menu
Docs Home
/ /
/ / /

Batch Changes with Transactions

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() and SaveChangesAsync() 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 calling Commit() or Rollback().

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.

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, set AutoTransactionBehavior.Never as 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.

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();

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

WhenNeeded

The provider uses a transaction only when SaveChanges() or SaveChangesAsync() affects more than one root entity. This is the default value.

Always

The provider always uses a transaction, even for single-entity operations.

Never

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();

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.

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

ReadConcern

ReadConcern

Read concern for the first command in the transaction. The default value is null. If you don't set this property, the provider inherits the value from the session's default transaction options or the MongoClient.

ReadPreference

ReadPreference

Read preference for read operations in the transaction. Transactions must read from the primary. The default value is null. If you don't set this property, the provider inherits the value from the session's default transaction options or the MongoClient.

WriteConcern

WriteConcern

Write concern for the commitTransaction and abortTransaction commands. The default value is null. If you don't set this property, the provider inherits the value from the session's default transaction options or the MongoClient.

MaxCommitTime

TimeSpan?

Maximum amount of time to allow a single commitTransaction command to run. The default is null. If you don't set this property, the provider doesn't send a commit time limit.

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.

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() or BeginTransactionAsync() while a transaction is already active, the provider throws an InvalidOperationException.

  • 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 transactionLifetimeLimitSeconds server 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 TransientTransactionError error. 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.

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:

Back

Write Data

On this page