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.
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 viaWithTableName(...), 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, andDB.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), andInstanceExistsAsync().[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
nullfor 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_migrationsrow 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
PreviousIdchain, 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
Guidname, 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/FieldCryptoalso expose the optionalassociatedDatafor custom use. See Encrypted columns. - Field encryption: key zeroing & portable format.
AesFieldEncryptorimplementsIDisposable(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
InsertMultipleAsyncon the database context.I{Table}Setnow exposesInsertMultipleAsync(entities, includeAutoFields, ct), batching a whole collection into multi-rowINSERTs within the unit-of-work scope. See Database context → Table-set methods.- Auto-field control on context inserts.
InsertAsyncandInsertMultipleAsynctakeincludeAutoFields(defaultfalse); passtrueto also write auto-increment columns (supply your own values), the context equivalent ofWithAllFields(). Backed by a newGetInsertPlan(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 realSELECT COUNT(*), replacing the previous client-side drain), plusSumAsync/AvgAsync/MinAsync/MaxAsyncand a single-valueScalarAsync<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.HashiCorppackage independently of the main package (each is version-checked separately, so a re-run still ships one when the other is already published).