Table of Contents

SquidStd.Database

FreeSql-backed implementation of the SquidStd.Database.Abstractions contracts. It owns a singleton IFreeSql, exposes a generic FreeSqlDataAccess<TEntity> with transactional writes (rollback on failure), bulk operations and paging, parses URI-style connection strings, and can auto-sync the schema on startup.

Install

dotnet add package SquidStd.Database

Usage

using DryIoc;
using SquidStd.Database.Abstractions.Interfaces.Data;
using SquidStd.Database.Extensions;

var container = new Container();
container.RegisterDatabase(); // reads the "database" config section

// DatabaseConfig: ConnectionString = "postgres://user:pass@host:5432/app", AutoMigrate = true
var users = container.Resolve<IDataAccess<User>>();
await users.InsertAsync(new User { Name = "Ann" });
var page = await users.GetPagedAsync(page: 1, pageSize: 20, orderBy: u => u.Name);

Key types

Type Purpose
RegisterDatabaseExtension RegisterDatabase() DI registration.
DatabaseService Owns the singleton IFreeSql; builds it and (optionally) migrates.
FreeSqlDataAccess<TEntity> FreeSql IDataAccess<TEntity> implementation.
ConnectionStringParser URI → provider + native connection string.
ZLinqResultExtensions Zero-alloc in-memory result helpers.

Seeding

Seeders populate initial database data once per seeder name. Each seeder's name is tracked in the __squidstd_seed_history table; a seeder runs only if its name does not yet exist in the history. The history row is written only after a successful run - if a seeder throws, it is retried at the next process start. A seeder exception aborts startup (fail-fast). The seed and history row are not wrapped in a transaction, so a seeder should tolerate running again over data it already wrote.

The history table is created during database service start when at least one seeder is registered, independent of whether AutoMigrate is on or off. Seeder names must be unique; the startup duplicate check is ordinal (case-sensitive), while the history-table lookup for a given seeder's name follows the database's collation.

Delegate seeder

Register an inline seeding callback with a unique name:

bootstrap.ConfigureServices(c =>
{
    c.RegisterDatabase();  // binds "database" config, runs seeders after init
    
    // Inline delegate seeder
    c.RegisterDatabaseSeeder("accounts.admin", async (database, ct) =>
    {
        await database.Orm.Insert(new User { Id = 1, Name = "Admin" }).ExecuteAffrowsAsync(ct);
    });
    
    return c;
});

Class seeder

Implement IDatabaseSeeder (which provides the name) and register it by type:

public sealed class AdminAccountSeeder : IDatabaseSeeder
{
    public string Name => "accounts.admin";
    
    public async ValueTask SeedAsync(IDatabaseService database, CancellationToken cancellationToken = default)
    {
        await database.Orm.Insert(new User { Id = 1, Name = "Admin" }).ExecuteAffrowsAsync(cancellationToken);
    }
}

bootstrap.ConfigureServices(c =>
{
    c.RegisterDatabase();
    c.RegisterDatabaseSeeder<AdminAccountSeeder>();
    
    return c;
});

Key semantics

  • Run-once per name: The __squidstd_seed_history table tracks which seeders have successfully run. A seeder is skipped if its name already exists in the history.
  • Failed-seeder retry: The history row is written only after the seeder succeeds. If a seeder throws, the row is not written, and the seeder retries at the next process start.
  • History table creation: The table is created during database service start when at least one seeder is registered (this is what triggers RunSeedersAsync), independent of whether AutoMigrate is on or off. Its schema is internal to SquidStd and should not be modified.
  • Duplicate name check: Seeder names must be unique. Duplicate names (ordinal, case-sensitive match) are detected at startup and cause an exception.
  • Execution order: Seeders run in registration order. Multiple seeders can be registered via chained RegisterDatabaseSeeder() calls.
  • No transaction wrap: The seed and its history row are not wrapped in a transaction. Seeders should be idempotent or accept re-running over partially-written state.

License

MIT - part of SquidStd.