/DB

Changelog

What changed in Socigy.OpenSource.DB. Runtime-named typed tables ([TableType] and DynamicTable) in 0.3.2, the database-context bulk insert plus scalar and aggregate API in 0.3.1, and 0.3.0's field encryption, rotating credentials, and HashiCorp Vault package.

updated 6 Jun 20263 min readv0.3.2View as Markdown

v0.3.2 (unreleased)

Added

  • Dynamic (runtime-named) tables. New [TableType] attribute: declare a typed column shape once and bind the table name at runtime via WithTableName(...), returning the typed entity (NativeAOT-safe). Full CRUD + aggregates, both standalone (WithConnection/WithTransaction) and through a context (db.DynamicTable<T>(name)). See Dynamic tables.
  • Custom (undeclared) columns. WithCustomColumns(...) captures extra runtime columns into each row, TryGetCustomValue<T>(...) reads them, and DB.CustomField<T>("name") filters on them inside a normal predicate. MapTypeAsync(name, conn) auto-discovers a table's extra columns once and caches the schema.
  • Runtime table lifecycle. InstantiateAsync() (CREATE TABLE from the declared shape), DeleteInstanceAsync() (DROP TABLE), and InstanceExistsAsync(). [TableType] tables live outside the migration history, so the type manages its own DDL.
  • Extended join builders. Joins now support 3 and 4 tables (chain .Join<T3>(…).Join<T4>(…)), OrderBy/OrderByDesc, client-side projection (.Select((a,b,…)=>…) → a typed result), and aggregates (CountAsync/SumAsync/AvgAsync/MinAsync/MaxAsync). See Joins.

Fixed

  • Outer joins now return null for an unmatched side (was a zeroed default instance). Join tuple elements are nullable, so you can distinguish "no match" from a real row of defaults.
  • Query(pred).Join<…>(…) now filters the driving table. The driving predicate was previously dropped.
  • Migrations apply atomically. Each migration's schema change and its _scg_migrations row now commit (or roll back) in a single transaction, so a crash can no longer leave the schema changed-but-unrecorded or recorded-but-not-changed. See Applying migrations.
  • Migration order follows the PreviousId chain, not id sorting. Ids are minute-granularity timestamps; two migrations created in the same minute (or any non-sortable id) could previously apply out of order. A broken or forked chain now fails loudly.
  • Rollback-aware version detection. The current version is computed from the full history honoring is_rollback, so a rolled-back migration is no longer reported as current.
  • Deterministic constraint names. Column-less constraints (e.g. raw CHECKs) no longer get a random Guid name, so regenerated migrations are reproducible.

Changed

  • Destructive and lossy migration statements are flagged. Generated migrations prefix data-losing operations with -- [SOCIGY:DESTRUCTIVE] (table/column drops) or -- [SOCIGY:LOSSY] (narrowing/unsafe type casts), and the CLI lists them at generation time. See Schema generation.

Security

  • Field encryption: associated-data binding (automatic). Generated code now binds every encrypted value to its table:column (authenticated into the HMAC, not stored), so a value cannot be relocated to a different column/row and still decrypt. IFieldEncryptor/FieldCrypto also expose the optional associatedData for custom use. See Encrypted columns.
  • Field encryption: key zeroing & portable format. AesFieldEncryptor implements IDisposable (zeroes key material) and now encodes values in a fixed little-endian byte order so ciphertext is portable across architectures.
  • Vault: auth token is kept alive. A background service renews the Vault token (renew-self, or AppRole relogin at max TTL) so long-running apps no longer fail once the initial token expires.
  • Vault: credential renewal tracks the real lease TTL (renews at ~2/3 of the lease) instead of a fixed interval that could outlast it.
  • Vault: connection strings are built with DbConnectionStringBuilder, so leased passwords containing ;, =, quotes or spaces are escaped correctly.
  • Vault: a warning is logged when the Vault address uses plaintext HTTP to a non-loopback host.

v0.3.1 (5 June 2026)

Added

  • InsertMultipleAsync on the database context. I{Table}Set now exposes InsertMultipleAsync(entities, includeAutoFields, ct), batching a whole collection into multi-row INSERTs within the unit-of-work scope. See Database context → Table-set methods.
  • Auto-field control on context inserts. InsertAsync and InsertMultipleAsync take includeAutoFields (default false); pass true to also write auto-increment columns (supply your own values), the context equivalent of WithAllFields(). Backed by a new GetInsertPlan(bool includeAutoIncrement).
  • Projecting ForEachAsync<TResult>. Streams matching rows, projects each through the callback, and returns the results (materialized inside the scope), so you can transform rows without a lazy enumerable escaping the connection.
  • Scalar & aggregate queries. CountAsync (a real SELECT COUNT(*), replacing the previous client-side drain), plus SumAsync/AvgAsync/MinAsync/MaxAsync and a single-value ScalarAsync<T>, on both the query builder and the database context, parameterized via the existing WHERE translation. See Aggregates & scalars.

Fixed

  • CI now builds, packs, and publishes the optional Socigy.OpenSource.DB.HashiCorp package independently of the main package (each is version-checked separately, so a re-run still ships one when the other is already published).