How to handle Transaction in .NET Core?

Asked 1 month ago Updated 8 days ago 131 views

1 Answer


0

Handling transactions in .NET Core (especially with Entity Framework Core) is straightforward once you understand when to rely on automatic behavior and when to take manual control.

How to handle Transaction in .NET Core?

1. Default Behavior (Auto Transactions)

When you call:

await dbContext.SaveChangesAsync();

Entity Framework Core automatically wraps all changes in a transaction.

Example

using (var context = new AppDbContext())
{
    context.Users.Add(new User { Name = "A" });
    context.Orders.Add(new Order { Amount = 100 });

    await context.SaveChangesAsync(); // Auto transaction
}
  • If anything fails → everything rolls back
  • If success → everything commits

Use this for simple CRUD operations

2. Manual Transaction (Full Control)

Use this when:

  • Multiple SaveChanges() calls
  • Cross-service logic
  • Complex business operations

Example

using (var context = new AppDbContext())
{
    using (var transaction = await context.Database.BeginTransactionAsync())
    {
        try
        {
            context.Users.Add(new User { Name = "A" });
            await context.SaveChangesAsync();

            context.Orders.Add(new Order { Amount = 100 });
            await context.SaveChangesAsync();

            await transaction.CommitAsync();
        }
        catch (Exception)
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}
  • Gives you fine-grained control
  • Useful for multi-step operations

3. Using TransactionScope (Cross-Context / Distributed)

Use TransactionScope when:

  • Multiple DB contexts
  • Multiple databases
  • External services involved

Example

using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    using (var context1 = new AppDbContext())
    {
        context1.Users.Add(new User { Name = "A" });
        await context1.SaveChangesAsync();
    }

    using (var context2 = new AppDbContext())
    {
        context2.Orders.Add(new Order { Amount = 200 });
        await context2.SaveChangesAsync();
    }

    scope.Complete();
}

Important:

  • Requires TransactionScopeAsyncFlowOption.Enabled for async
  • Can escalate to distributed transaction (MSDTC) → expensive

4. Handling Isolation Levels

You can control concurrency behavior:

using (var transaction = await context.Database.BeginTransactionAsync(
    System.Data.IsolationLevel.Serializable))
{
    // Your logic
}

Common Levels

  • ReadCommitted (default) → safe + fast
  • RepeatableRead → prevents re-reading changes
  • Serializable → strictest (but slowest)

5. Best Practices (Production Level)

Keep transactions short

  • Avoid long-running operations inside transactions.

Avoid external API calls inside transaction

Bad:

await transaction.BeginAsync();
await CallExternalAPI(); // risky

Use retry policies

Combine with:

  • Polly
  • EF Core execution strategies

Use one DbContext per transaction

  • DbContext is NOT thread-safe

6. Advanced Pattern (Execution Strategy + Transaction)

For resilient systems (like SQL Azure):

var strategy = context.Database.CreateExecutionStrategy();

await strategy.ExecuteAsync(async () =>
{
    using (var transaction = await context.Database.BeginTransactionAsync())
    {
        try
        {
            context.Users.Add(new User { Name = "RetryUser" });
            await context.SaveChangesAsync();

            await transaction.CommitAsync();
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
});

7. When to Use What?

Scenario Approach
Simple insert/update Auto transaction
Multiple SaveChanges Manual transaction
Multiple DB / services TransactionScope
Cloud + retry needed ExecutionStrategy

Final Insight

  • 80% cases → SaveChanges() is enough
  • 15% → Manual transaction
  • 5% → Distributed transactions

Write Your Answer