--- title: "Transactions" description: "Use transactions to group database operations atomically." canonical: "https://roomsharp.dev/docs/v0.5.4/transactions" source: "src/content/v0.5.4/transactions.mdx" --- # Transactions RoomSharp provides multiple ways to work with transactions for atomic database operations. ## Transaction Helpers Use the built-in transaction helpers from `RoomDatabase`: - `RunInTransaction(Action, CancellationToken ct = default)` - Synchronous action - `RunInTransaction(Func, CancellationToken ct = default)` - Synchronous function with return value - `RunInTransactionAsync(Func)` - Async action without caller cancellation - `RunInTransactionAsync(Func, CancellationToken ct)` - Async action with caller cancellation - `RunInTransactionAsync(Func>)` - Async function with return value - `RunInTransactionAsync(Func>, CancellationToken ct)` - Async function with return value and caller cancellation Cancellation tokens cancel transaction setup and gate waiting. They do not automatically cancel the delegate body; pass the same token to DAO calls inside the transaction when the body should be cancellable too. In serialized mode, async overloads use the token while waiting for the transaction lock, entering the database gate, and opening the transaction. Synchronous overloads can cancel while waiting for the transaction lock. In parallel mode, async overloads pass the token to `BeginUnitOfWorkAsync`. Transactional seeders, DAO calls, and journal writes now share the same active transaction/session when seeding runs transactionally. Serialized async transactions also use async gate/transaction paths with caller cancellation while waiting for the transaction lock. ```csharp // Synchronous db.RunInTransaction(() => { db.UserDao.Insert(user1); db.UserDao.Insert(user2); }, cancellationToken); // With return value var count = db.RunInTransaction(() => { db.UserDao.Insert(user1); return db.UserDao.GetCount(); }); // Async await db.RunInTransactionAsync(async () => { await db.UserDao.InsertAsync(user1, cancellationToken); await db.UserDao.InsertAsync(user2, cancellationToken); }, cancellationToken); // Async with return value var result = await db.RunInTransactionAsync(async () => { await db.UserDao.InsertAsync(user1); return await db.UserDao.FindByIdAsync(user1.Id); }); ``` ## [Transaction] Attribute Mark DAO methods with `[Transaction]` to have the generator wrap the emitted body in the appropriate helper (sync/async). The code generator handles `Task`, `Task`, and synchronous methods automatically. ```csharp [Dao] public interface IUserDao { [Insert] long Insert(User user); [Update] int Update(User user); [Query("SELECT * FROM users WHERE Email = :email")] Task FindByEmailAsync(string email); [Transaction] async Task UpsertAsync(User user) { var existing = await FindByEmailAsync(user.Email); if (existing is null) { return Insert(user); } existing.Name = user.Name; Update(existing); return existing.Id; } [Query("DELETE FROM users WHERE IsActive = 0")] Task DeleteInactiveAsync(); [Transaction] async Task ReplaceAllAsync(IEnumerable users) { await DeleteInactiveAsync(); var count = 0; foreach (var user in users) { Insert(user); count++; } return count; } } ``` The `[Transaction]` attribute is ideal for methods that combine multiple DAO operations that must succeed or fail together. `[Transaction]` cannot be applied to `IAsyncEnumerable` streaming DAO methods. Streaming keeps the data reader alive after the method returns, so the generator reports a compile-time diagnostic instead of creating an unclear transaction lifetime. ## Rollback Behavior Transactions automatically rollback when: - An exception is thrown within the transaction scope - The transaction is not explicitly committed ```csharp try { await db.RunInTransactionAsync(async () => { await db.UserDao.InsertAsync(user1); // This will throw, causing rollback throw new InvalidOperationException("Something went wrong"); await db.UserDao.InsertAsync(user2); // Never executed }); } catch (InvalidOperationException) { // user1 was NOT inserted due to rollback } ``` ## UnitOfWork (Parallel Mode) When using Parallel concurrency mode, use `BeginUnitOfWorkAsync` for explicit transaction control: ```csharp await using var uow = await db.BeginUnitOfWorkAsync(); try { await db.UserDao.InsertAsync(new User { Email = "a@b.com" }); await db.UserDao.InsertAsync(new User { Email = "b@c.com" }); await uow.CommitAsync(); } catch { // Automatic rollback on dispose if not committed } ``` In Parallel mode, parallel DAO calls inside an active UnitOfWork reuse the ambient session and run serially. Do not attempt parallel execution within the same UnitOfWork. ## Best Practices 1. **Keep transactions short** - Long-running transactions can cause lock contention 2. **Use `[Transaction]` for DAO methods** - Let the generator handle the wrapping 3. **Handle exceptions properly** - Always wrap transaction code in try-catch when you need to handle failures 4. **Avoid unnecessary nesting** - nested transaction helpers join the active ambient transaction; they do not create an independent inner commit/rollback boundary 5. **Test rollback scenarios** - Ensure your application handles transaction failures gracefully