liveBook · Manning

Database transaction

As explained in .NET in Action, Second Edition

A transaction is a database operation boundary whose changes are committed together or discarded together to prevent partial completion.

A database transaction groups related updates so they either all commit or all roll back. Consider a transfer that credits one account and debits another. If the debit fails after the credit is saved, the balances are wrong unless both updates share a transaction. The database manages that boundary even though the application, its network connection, or its host can fail. section 11.2.1

A transfer that fails halfway

In an EF Core repository, CreditAsync changes a balance and calls SaveChangesAsync; DebitAsync does the same but first rejects insufficient funds. Calling them in sequence without a shared transaction can save the credit before the debit throws. A test transferring more than the source account holds should find both balances unchanged, not just catch the exception. section 11.2.1

Listing 11.11 A transactional version of the BankRepository.TransferAsync
public async Task TransferAsync(
  string from, string to, decimal amt)
{
  using var tx = await
    _ctxt.Database.BeginTransactionAsync();    #1
  
  try
  {
    await CreditAsync(to, amt);                #2
    await DebitAsync(from, amt);
    await tx.CommitAsync();                    #3
  }
  catch                                        #4
  {
    await tx.RollbackAsync();                  #5
    _ctxt.ChangeTracker.Clear();               #6
    throw;                                     #7
  }
}

The transactional TransferAsync begins an EF Core transaction before either account update, commits after both succeed, and rolls back on an exception. It also clears the change tracker before rethrowing: tracked account objects may otherwise still reflect changes that the database discarded. section 11.2.1

Retries add a second boundary to consider. EF Core’s SQL Server execution strategy can retry transient faults, but enabling it does not make the default strategy handle an explicitly started transaction. Put the whole transaction inside an execution strategy when it must be retried. If the connection fails during commit, the application may not know whether the transaction succeeded; when possible, verify its outcome before repeating the work. section 11.2.2

Listing 11.13 Transaction execution strategies
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
  
namespace MoneyTransfer;
  
public static class Extensions
{
  public static Task ExecuteRetryableTransactionAsync(   #1
    this DbContext ctxt,                                 #2
    Func<Task> action)                                   #3
  {
    IExecutionStrategy strategy =
      ctxt.Database.CreateExecutionStrategy();           #4
    return strategy.ExecuteAsync(async () =>
    {
      using IDbContextTransaction tx =
        await ctxt.Database.BeginTransactionAsync();
      try
      {
        await action();                                  #5
        await tx.CommitAsync();
      }
      catch
      {
        await tx.RollbackAsync();
        ctxt.ChangeTracker.Clear();                      #6
        throw;                                           #7
      }
    });
  }
  
  public static Task ExecuteVerifyingTransactionAsync(   #8
    this DbContext ctxt,
    Func<Task> action,
    Func<Task<bool>> didItWork)                          #9
  {
    IExecutionStrategy strategy =
      ctxt.Database.CreateExecutionStrategy();
    return strategy.ExecuteInTransactionAsync(           #10
      action, didItWork);
  }
}

The transaction helper obtains an execution strategy, starts the transaction inside its operation, runs the supplied action, and commits. Its exception path rolls back and clears EF Core’s change tracker. A verifying variant checks the result when commit leaves the outcome uncertain. section 11.2.2

How .NET in Action, Second Edition covers it

How deep it goes
Works through separate saves, a failing test, explicit commit and rollback, change-tracker cleanup, and transaction-aware retry.
Examples
Uses a C# account-transfer repository, an insufficient-funds test, and EF Core transaction-helper listings.
What it assumes
Assumes familiarity with C# asynchronous methods while building the account entity, DbContext, and repository used in the example.
What you can do after
Apply it via EF Core to commit or roll back account updates together and account for retries after uncertain commits.
How it connects
Connects EF Core change tracking and SaveChangesAsync with execution strategies, transient faults, and commit-outcome verification.

Also explained in

Compare how 14 books explain database transaction →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of .NET in Action, Second Edition. The section links point to the parts of the book it draws on.