Quickstart
Declare a table, wire up the generated context, then insert, read, update, and delete rows through ISocigyDatabaseFactory.
Prerequisites
- .NET 10 SDK installed and on your PATH.
- A running PostgreSQL 14+ instance, currently the only supported engine.
Socigy.OpenSource.DBadded to your project. See Installation.
This quickstart uses the recommended context API: you inject a factory, run work inside a scope, and the connection is managed for you. For raw connection access see Connections & DI.
1. Declare a table
Annotate a partial class with [Table] and column attributes. The class must be partial so the source generator can augment it with query methods in a companion file.
using Socigy.OpenSource.DB.Attributes;
[Table("users")]
public partial class User
{
[PrimaryKey, Default(DbDefaults.Guid.Random)]
public Guid Id { get; set; }
public string Username { get; set; } = "anonymous";
public string? Email { get; set; }
[Default(DbDefaults.Time.Now)]
public DateTime CreatedAt { get; set; }
}= "anonymous" is more than a C# default. The generator reads it and emits DEFAULT 'anonymous' in the DDL, so the database enforces the same default even for rows inserted outside your application.DbDefaults constants come from Socigy.OpenSource.DB.Attributes, a single using.Build the project once to run the source generator:
dotnet build2. Generate the schema
Build the model project in the DB_Migration configuration to produce the PostgreSQL DDL from your annotated classes:
dotnet build MyApp.DB/MyApp.DB.csproj -c DB_MigrationThe migration for the User class above resolves to:
CREATE TABLE IF NOT EXISTS "users" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"username" TEXT NOT NULL DEFAULT 'anonymous',
"email" TEXT,
"created_at" TIMESTAMP NOT NULL DEFAULT timezone('utc', now()),
PRIMARY KEY ("id")
);See CLI tool and Applying migrations for the full workflow.
3. Register the database and context
With databaseName: "AppDb" in socigy.json, the generator emits AddAppDb(), AddAppDbContext(), and EnsureLatestAppDbMigration(). Wire them up at startup:
using Socigy.OpenSource.DB.AppDb.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.AddAppDb(); // registers IDbConnectionFactory
builder.Services.AddAppDbContext(); // registers ISocigyDatabaseFactory<IAppDb>
var app = builder.Build();
await app.EnsureLatestAppDbMigration(); // applies pending migrations on startupThe connection string lives in appsettings.json under ConnectionStrings:AppDb:Default. Do not include Database=, the factory appends it from databaseName:
{
"ConnectionStrings": {
"AppDb": {
"Default": "Host=localhost;Port=5432;Username=postgres;Password=secret"
}
}
}See Configuration for every field.
4. Inject the factory
Inject ISocigyDatabaseFactory<IAppDb> and run work inside a scope. ExecuteAsync opens a non-transactional scope, ExecuteTransactionAsync commits when the callback returns and rolls back if it throws. Table sets are pluralized: User becomes db.Users.
using Socigy.OpenSource.DB.AppDb.Context;
using Socigy.OpenSource.DB.Core.Context;
public class UserService(ISocigyDatabaseFactory<IAppDb> db)
{
// ...
}5. Insert a row
InsertAsync skips columns with a [Default] attribute or a C# initializer by default, letting PostgreSQL generate Id and CreatedAt:
await db.ExecuteAsync(d => d.Users.InsertAsync(
new User { Username = "alice", Email = "[email protected]" }));InsertAsync returns Task<bool>. To write the auto fields yourself (for example a pre-generated primary key), pass includeAutoFields: true:
await db.ExecuteAsync(d => d.Users.InsertAsync(
new User { Id = Guid.NewGuid(), Username = "bob" },
includeAutoFields: true));Batch many rows with InsertMultipleAsync, which emits multi-row INSERT statements:
await db.ExecuteTransactionAsync(d => d.Users.InsertMultipleAsync(newUsers));6. Read rows
ToListAsync materializes matching rows, and FirstOrDefaultAsync returns a single row or null. Both run inside the scope, so nothing lazy escapes the connection:
var alice = await db.ExecuteAsync(d =>
d.Users.FirstOrDefaultAsync(u => u.Username == "alice"));
var recent = await db.ExecuteAsync(d =>
d.Users.ToListAsync(u => u.CreatedAt > DateTime.UtcNow.AddDays(-7)));Omit the predicate on ToListAsync to fetch every row. Use ExistsAsync and CountAsync for existence and tallies:
bool taken = await db.ExecuteAsync(d => d.Users.ExistsAsync(u => u.Username == "alice"));
long total = await db.ExecuteAsync(d => d.Users.CountAsync());For streaming large result sets row by row, see ForEachAsync in Database context. For richer reads, see SELECT and WHERE.
7. Update a row
UpdateAsync matches on the primary key and returns the number of affected rows:
alice.Username = "alice-v2";
int updated = await db.ExecuteAsync(d => d.Users.UpdateAsync(alice));See UPDATE for partial-column updates.
8. Delete rows
DeleteAsync takes a predicate and returns the number of deleted rows:
int deleted = await db.ExecuteAsync(d =>
d.Users.DeleteAsync(u => u.Username == "alice-v2"));See DELETE for more.
Next steps
- Build the same concepts into a full application in the Tutorial.
- Learn every modeling attribute in Tables and Columns.
- Group multiple writes atomically with Transactions.
- Explore joins and set operations in Joins and Set operations.
- Solve common problems with Recipes.