Transactions - pengdows/pengdows.crud GitHub Wiki
pengdows.crud provides full transaction support via a dedicated TransactionContext, which implements IDatabaseContext and ensures that all SQL operations executed within its scope are atomic and isolated.
To create a transaction, request a TransactionContext from an existing DatabaseContext:
await using var tx = await context.BeginTransactionAsync();
By default, this will use the minimum required isolation level:
- `READ COMMITTED` for write operations
- `REPEATABLE READ` for read-only connections
await using var tx = await context.BeginTransactionAsync(IsolationLevel.Serializable);
Within a transaction, any `EntityHelper` or `SqlContainer` created from the `TransactionContext` will participate in the open transaction:
var helper = new EntityHelper<User, Guid>(tx, serviceProvider); await helper.CreateAsync(user); var sc = tx.CreateSqlContainer(); // build and execute query...
The connection will remain open for the duration of the transaction, and will not be automatically closed after each command.
Transactions must be explicitly committed:
await tx.CommitAsync();
If the `TransactionContext` is disposed without being committed, it will automatically roll back:
await using var tx = await context.BeginTransactionAsync(); // ... something fails or throws // No need to call Rollback explicitly
pengdows.crud enforces a minimum isolation level policy to ensure safe and repeatable behavior across databases:
- `READ COMMITTED` is the lowest allowed for write transactions
- `REPEATABLE READ` is enforced for read-only transactions
- `CockroachDB` uses `SERIALIZABLE` always, and ignores the requested level
The context will throw an exception if you attempt to:
- Run a `NonQuery` or `Scalar` on a read-only connection
- Run a `Reader` on a write-only connection
Nested TransactionContext
creation is supported, but the inner transaction is isolated and independent. There is no ambient or ambient-suppressed behavior like in Entity Framework.
await using var tx = await context.BeginTransactionAsync(); try { var userHelper = new EntityHelper<User, Guid>(tx, sp); await userHelper.CreateAsync(newUser); var logHelper = new EntityHelper<AuditLog, Guid>(tx, sp); await logHelper.CreateAsync(newLog); await tx.CommitAsync(); } catch { // auto-rollback happens on dispose throw; }