/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 26 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).