Transactions
CloudStorageORM provides transaction support through a durable transaction journal and replay mechanism. This guide explains how transactions work and how to use them.
Transaction basics
Transactions in CloudStorageORM are context-scoped and durable. Each transaction:
- Gets a unique
TransactionId(Guid) - Stages changes to a manifest on storage
- Can be committed, rolled back, or recovered after interruption
Starting a transaction
var transaction = await context.Database.BeginTransactionAsync();
Committing a transaction
try
{
var user = new User { Name = "Alice", Email = "alice@example.com" };
context.Add(user);
await context.SaveChangesAsync();
await transaction.CommitAsync();
// Changes are now durably persisted
}
catch (Exception)
{
await transaction.RollbackAsync();
throw;
}
Rolling back
try
{
// Make some changes
context.Add(entity);
await context.SaveChangesAsync();
}
catch
{
await transaction.RollbackAsync();
// Changes are discarded
}
Transaction lifecycle
Phase 1: Preparing
When you call SaveChangesAsync() inside an active transaction:
__cloudstorageorm/tx/<transactionId>/manifest.json
{
"state": "Preparing",
"operations": [
{ "type": "SaveAsync", "entity": "..." },
{ "type": "DeleteAsync", "entity": "..." }
]
}
Phase 2: Committing
When you call CommitAsync():
- Manifest state changes to
Committed - Operations are replayed in sequence
- Manifest state changes to
Completed
Phase 3: Recovery
On startup of a new transaction manager instance:
- Completed manifests: Are treated as finalized/completed state
- Preparing manifests: Are marked as
Aborted(uncommitted work is discarded) - Committed manifests: Are replayed to ensure durability
Example: Transaction with retry
var maxRetries = 3;
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
using var transaction = await context.Database.BeginTransactionAsync();
var user = new User { Name = "Bob", Email = "bob@example.com" };
context.Add(user);
await context.SaveChangesAsync();
await transaction.CommitAsync();
break; // Success
}
catch (Exception ex)
{
await transaction.RollbackAsync();
retryCount++;
if (retryCount >= maxRetries) throw;
}
}
Limitations
- Only one active transaction per
DbContextinstance - Transactions are not distributed across contexts
- Provider-native temporary locking (for example, Azure leases) is not yet implemented
Future enhancements
See Roadmap for planned improvements:
- v1.1.0: Provider-native locking (Azure leases, AWS conditional locks)
- v1.2.0: Enhanced AWS transaction reliability
- v1.4.0: Snapshot and point-in-time recovery