EfCore.InMemory.Transactions
1.0.0
dotnet add package EfCore.InMemory.Transactions --version 1.0.0
NuGet\Install-Package EfCore.InMemory.Transactions -Version 1.0.0
<PackageReference Include="EfCore.InMemory.Transactions" Version="1.0.0" />
<PackageVersion Include="EfCore.InMemory.Transactions" Version="1.0.0" />
<PackageReference Include="EfCore.InMemory.Transactions" />
paket add EfCore.InMemory.Transactions --version 1.0.0
#r "nuget: EfCore.InMemory.Transactions, 1.0.0"
#:package EfCore.InMemory.Transactions@1.0.0
#addin nuget:?package=EfCore.InMemory.Transactions&version=1.0.0
#tool nuget:?package=EfCore.InMemory.Transactions&version=1.0.0
EfCore.InMemory.Transactions
Seamless transaction support for EF Core InMemory provider. Eliminates "transactions with isolation level are not supported" errors in tests without changing production code.
The Problem
EF Core's InMemory provider throws exceptions when using transactions with isolation levels:
// This FAILS with InMemory provider:
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.Serializable, ct);
// Exception: "Transactions with isolation level Serializable are not supported"
This forces you to either:
- ❌ Switch to SQLite in-memory (different provider, different behavior)
- ❌ Add
if (IsInMemory)checks throughout your production code - ❌ Restructure your code to not use transactions in tests
The Solution
This package provides two approaches:
Approach 1: Safe Extension Methods (Recommended for Direct DbContext Usage)
// Instead of:
await context.Database.BeginTransactionAsync(IsolationLevel.Serializable, ct);
// Use:
await context.Database.BeginTransactionSafeAsync(IsolationLevel.Serializable, ct);
// ✅ Works with both real database and InMemory!
Approach 2: UnitOfWork Pattern (Recommended for Clean Architecture)
If you use the UnitOfWork pattern, integrate InMemory detection in your UnitOfWork class:
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private readonly bool _isInMemoryDatabase;
public UnitOfWork(AppDbContext context)
{
_context = context;
_isInMemoryDatabase = _context.Database.IsInMemoryDatabase(); // Extension method
}
public Task<IDbContextTransaction> BeginTransactionAsync(
IsolationLevel isolationLevel,
CancellationToken cancellationToken = default)
{
if (_isInMemoryDatabase)
{
return Task.FromResult<IDbContextTransaction>(new NoOpDbContextTransaction());
}
return _context.Database.BeginTransactionAsync(isolationLevel, cancellationToken);
}
}
Installation
dotnet add package EfCore.InMemory.Transactions
Or via Package Manager:
Install-Package EfCore.InMemory.Transactions
Quick Start
Step 1: Configure Test DbContext
// In CustomWebApplicationFactory.cs or test setup:
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString())
.AddInMemoryTransactionSupport(); // Suppresses warnings
});
Step 2: Use Safe Transaction Methods
// In your code (works with both real DB and InMemory):
await using var transaction = await context.Database
.BeginTransactionSafeAsync(IsolationLevel.Serializable, ct);
try
{
// ... do work ...
await context.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
}
catch
{
await transaction.RollbackAsync(ct);
throw;
}
API Reference
Extension Methods
// Check if using InMemory provider
bool isInMemory = context.Database.IsInMemoryDatabase();
// Safe transaction methods (work with any provider)
context.Database.BeginTransactionSafe();
context.Database.BeginTransactionSafe(IsolationLevel.Serializable);
await context.Database.BeginTransactionSafeAsync(ct);
await context.Database.BeginTransactionSafeAsync(IsolationLevel.Serializable, ct);
// Suppress transaction warnings in configuration
optionsBuilder.AddInMemoryTransactionSupport();
NoOpDbContextTransaction
A no-op implementation of IDbContextTransaction for use in UnitOfWork patterns:
// Use in your UnitOfWork when InMemory is detected:
if (_isInMemoryDatabase)
{
return new NoOpDbContextTransaction();
}
Full Example: ASP.NET Core Integration Tests
CustomWebApplicationFactory.cs
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove the real database registration
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
services.Remove(descriptor);
// Add InMemory database with transaction support
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString())
.AddInMemoryTransactionSupport();
});
});
}
}
UnitOfWork.cs (if using UnitOfWork pattern)
using EfCore.InMemory.Transactions;
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private readonly bool _isInMemoryDatabase;
public UnitOfWork(AppDbContext context)
{
_context = context;
_isInMemoryDatabase = context.Database.IsInMemoryDatabase();
}
public async Task<IDbContextTransaction> BeginTransactionAsync(
IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
CancellationToken cancellationToken = default)
{
if (_isInMemoryDatabase)
{
// Return no-op transaction for InMemory provider
return new NoOpDbContextTransaction();
}
return await _context.Database.BeginTransactionAsync(
isolationLevel, cancellationToken);
}
// ... rest of UnitOfWork implementation
}
Compatibility
| EF Core Version | .NET Version | Supported |
|---|---|---|
| 8.0 | .NET 8 | ✅ |
| 9.0 | .NET 9 | ✅ |
Why Not Just Use SQLite?
SQLite in-memory is a valid alternative, but:
| Aspect | InMemory + This Package | SQLite In-Memory |
|---|---|---|
| Setup Complexity | Simple | More complex |
| Speed | Fastest | Fast |
| Provider Behavior | InMemory behavior | SQLite behavior |
| Real Transactions | No (no-op) | Yes |
| Foreign Keys | No | Optional |
Use this package when: You want fast tests and your transaction logic is already tested elsewhere.
Use SQLite when: You need real transaction semantics in tests.
How It Works
IsInMemoryDatabase()- Checks if the provider name contains "InMemory"BeginTransactionSafeAsync()- ReturnsNoOpDbContextTransactionfor InMemory, real transaction otherwiseNoOpDbContextTransaction- ImplementsIDbContextTransactionwith no-op Commit/Rollback/DisposeAddInMemoryTransactionSupport()- SuppressesTransactionIgnoredWarning
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by the common pain point discussed in EF Core Issue #2866
- Created to solve real-world testing challenges with EF Core InMemory provider
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.0)
- Microsoft.EntityFrameworkCore.InMemory (>= 8.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.0)
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.InMemory (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 26 | 1/28/2026 |