![]() |
VOOZH | about |
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" />Directory.Packages.props
<PackageReference Include="EfCore.InMemory.Transactions" />Project file
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.0Install as a Cake Addin
#tool nuget:?package=EfCore.InMemory.Transactions&version=1.0.0Install as a Cake Tool
Seamless transaction support for EF Core InMemory provider. Eliminates "transactions with isolation level are not supported" errors in tests without changing production code.
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:
if (IsInMemory) checks throughout your production codeThis package provides two approaches:
// Instead of:
await context.Database.BeginTransactionAsync(IsolationLevel.Serializable, ct);
// Use:
await context.Database.BeginTransactionSafeAsync(IsolationLevel.Serializable, ct);
// ✅ Works with both real database and InMemory!
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);
}
}
dotnet add package EfCore.InMemory.Transactions
Or via Package Manager:
Install-Package EfCore.InMemory.Transactions
// In CustomWebApplicationFactory.cs or test setup:
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString())
.AddInMemoryTransactionSupport(); // Suppresses warnings
});
// 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;
}
// 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();
A no-op implementation of IDbContextTransaction for use in UnitOfWork patterns:
// Use in your UnitOfWork when InMemory is detected:
if (_isInMemoryDatabase)
{
return new NoOpDbContextTransaction();
}
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();
});
});
}
}
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
}
| EF Core Version | .NET Version | Supported |
|---|---|---|
| 8.0 | .NET 8 | ✅ |
| 9.0 | .NET 9 | ✅ |
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.
IsInMemoryDatabase() - Checks if the provider name contains "InMemory"BeginTransactionSafeAsync() - Returns NoOpDbContextTransaction for InMemory, real transaction otherwiseNoOpDbContextTransaction - Implements IDbContextTransaction with no-op Commit/Rollback/DisposeAddInMemoryTransactionSupport() - Suppresses TransactionIgnoredWarningContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 150 | 1/28/2026 |