# Transactions and tests

Commit several writes atomically with ExecuteTransactionAsync, then unit-test the data layer with no database by mocking the generated context interfaces.

The last endpoint creates a project and its first tasks together. Either all of it lands or none of it does, which is exactly what `ExecuteTransactionAsync` provides.

## One atomic unit of work

`ExecuteTransactionAsync` opens a transaction, runs the callback, **commits when it returns**, and **rolls back if it throws**. Move the multi-write logic into a service:

```csharp
public sealed class ProjectService(ISocigyDatabaseFactory<IAppDb> db)
{
    public Task<Guid> CreateWithTasksAsync(string name, IEnumerable<string> titles) =>
        db.ExecuteTransactionAsync(async d =>
        {
            var project = new Project { Id = Guid.NewGuid(), Name = name };
            await d.Projects.InsertAsync(project, includeAutoFields: true);

            foreach (var title in titles)
                await d.Tasks.InsertAsync(new TaskItem { ProjectId = project.Id, Title = title });

            return project.Id;   // committed here; any throw above rolls the whole thing back
        });
}
```

Generating the `Id` client-side and passing `includeAutoFields: true` writes that value, so the foreign key on each task is known before the rows are inserted. Register the service and wire the endpoint:

```csharp
builder.Services.AddScoped<ProjectService>();

app.MapPost("/projects/seeded", (SeedRequest body, ProjectService svc) =>
    svc.CreateWithTasksAsync(body.Name, body.Tasks));

record SeedRequest(string Name, string[] Tasks);
```

> **NOTE** Nested `ExecuteAsync` / `ExecuteTransactionAsync` calls join the outermost scope, so a service that calls another service still commits once. See [Transactions](/database/0.3.2/core-concepts/transactions).

## Test it without a database

Because the context is a set of generated interfaces (`ISocigyDatabaseFactory<IAppDb>`, `IAppDb`, `IProjectSet`, `ITaskItemSet`), the service is testable with a mocking library and no PostgreSQL. Wire the mock factory to invoke the callback against a mock context:

```csharp
[Fact]
public async Task CreateWithTasksAsync_inserts_project_and_each_task()
{
    var projects = new Mock<IProjectSet>();
    var tasks = new Mock<ITaskItemSet>();

    var ctx = new Mock<IAppDb>();
    ctx.SetupGet(c => c.Projects).Returns(projects.Object);
    ctx.SetupGet(c => c.Tasks).Returns(tasks.Object);

    var db = new Mock<ISocigyDatabaseFactory<IAppDb>>();
    db.Setup(f => f.ExecuteTransactionAsync(It.IsAny<Func<IAppDb, Task<Guid>>>(), It.IsAny<CancellationToken>()))
      .Returns((Func<IAppDb, Task<Guid>> work, CancellationToken _) => work(ctx.Object));

    var svc = new ProjectService(db.Object);
    await svc.CreateWithTasksAsync("Launch", new[] { "Design", "Build", "Ship" });

    projects.Verify(p => p.InsertAsync(It.IsAny<Project>(), true), Times.Once);
    tasks.Verify(t => t.InsertAsync(It.IsAny<TaskItem>(), false), Times.Exactly(3));
}
```

The mocked factory runs your real `CreateWithTasksAsync` logic, so the test verifies the unit of work itself: one project insert, one task insert per title. For tests against a real database, point a connection string at a disposable PostgreSQL instance and run migrations in a fixture. See [Unit testing](/database/0.3.2/testing/unit-testing).

## Where to go next

You have modelled a schema, migrated it, and read, written, and tested a typed data layer. From here:

- [Querying](/database/0.3.2/querying/reading/select) for the full standalone query builder, joins, and set operations.
- [Dynamic tables](/database/0.3.2/dynamic-tables/declaring) for per-tenant and time-partitioned tables.
- [Recipes](/database/0.3.2/recipes/keyset-pagination) for production patterns like pagination, soft deletes, and multi-tenancy.
