From 37f3de5c424bc5cb15334817b96f1cd48bb8e59f Mon Sep 17 00:00:00 2001 From: Sadeq Abu-Hattem Date: Mon, 2 Nov 2026 21:58:22 +0200 Subject: [PATCH] Prepare 1.0.0-preview.3 The changelog's Unreleased entries become the 1.0.0-preview.3 section, dated 2026-11-02, with a summary in the same shape as preview.2's: what the release adds (write participants and SettingWrite.GroupType), the two breaking changes and the step each takes, and the advice to pin an exact version. The clear entry is now marked breaking too, since an observer receives something different for a clear. The CodeQL change is CI only and ships nothing, so it is not listed. ISettingDbContext and ApplySettingsConfiguration are removed. preview.2 deprecated both "for removal in the next release", and their Obsolete messages said the same, so keeping them would have broken that promise. The three test cases that only checked the old name still built the same model go with them, as does the ChangeTracking using only ISettingDbContext needed. The upgrade note in storage-providers.md now says both are gone and what to do instead. Neither changes the model, so there is no migration to run. Verified: the solution builds with 0 warnings; unit (210 x2), provider (63 x2, down from 66 by the three removed cases) and API (51 x2) tests pass. Integration tests compiled but were not run locally; nothing they exercise changed. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 15 +++++- docs/storage-providers.md | 5 +- .../EfCore.cs | 46 ------------------- .../Stores/EfCoreModelConfigurationTests.cs | 45 ------------------ 4 files changed, 17 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57fbe65..5ebe52e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [1.0.0-preview.3] — 2026-11-02 + +The third prerelease. It adds what auditing needs from the library: `ISettingWriteParticipant`, +which commits its work in the same transaction as a settings write, and `SettingWrite.GroupType`. + +**Two breaking changes from `1.0.0-preview.2`.** The two APIs deprecated in preview.2 are +removed, as that release announced. And a clear now reports what it deleted, so an observer that +read an empty `Changes` as "this was a clear" must check `Kind` instead. Both entries below carry +the step to take. Still not frozen: pin an exact version rather than a floating range. + +### Removed +- **`ISettingDbContext` and `ApplySettingsConfiguration`, as BREAKING in prerelease.** Both were deprecated in `1.0.0-preview.2` for removal in this release. Delete `: ISettingDbContext` and its `DbSet` properties from your context, and rename `ApplySettingsConfiguration` to `ApplySettings`. Neither changes the model, so there is no migration to run. + ### Added - `SettingWrite.GroupType`: the settings class a write was made to. An observer that needs the group's attributes no longer has to scan assemblies for a class whose resolved name matches `GroupName`. It is an init property, so code that constructs a `SettingWrite` keeps compiling. - The library's first metrics, on a `DotNetBoost.Settings` meter: `dotnetboost.settings.write_observer.failures` and `dotnetboost.settings.change_handler.failures`, each tagged with the implementation's class name, the group and `error.type`. Observer and handler exceptions are still logged and swallowed; until now the log line was the only trace of them. The meter is created through the container's `IMeterFactory` when there is one. @@ -12,7 +25,7 @@ This project follows [Semantic Versioning](https://semver.org/). - `IEnlistingSettingStore`, the store capability participants require, and a `SettingBuilderGuard.EnsureProviderNotConfigured` overload through which a provider declares it. Only Entity Framework Core does. `Build()` refuses a participant for Dapper or MongoDB, and a write refuses one at run time for a store that cannot enlist, rather than run it with no atomicity. ### Changed -- **A clear now reports what it deleted.** A `Cleared` `SettingWrite` used to carry an empty `Changes`; it now lists one `SettingChange` per deleted row, with the stored value as `OldValue` and a null `NewValue`, redacted for `[Sensitive]` properties and for any encrypted row. The manager reads the group before deleting to do it, but only when a participant or observer is registered. An observer that relied on `Changes` being empty for a clear should check `Kind` instead. +- **A clear now reports what it deleted, as BREAKING in prerelease.** A `Cleared` `SettingWrite` used to carry an empty `Changes`; it now lists one `SettingChange` per deleted row, with the stored value as `OldValue` and a null `NewValue`, redacted for `[Sensitive]` properties and for any encrypted row. The manager reads the group before deleting to do it, but only when a participant or observer is registered. An observer that relied on `Changes` being empty for a clear should check `Kind` instead. - `EfCoreSettingStore.DeleteGroupAsync`, when a participant has enlisted something, runs the delete and a `SaveChangesAsync` in one transaction: the caller's if there is one, otherwise its own inside the context's execution strategy, so it works under `EnableRetryOnFailure`. With nothing enlisted it is the single `ExecuteDeleteAsync` it was. - Aspire moved from 13.5.2 to 13.6.4, and Microsoft.Extensions.* and EF Core packages from 10.0.11 to 10.0.12 — the floor Aspire 13.6 requires. - Microsoft.Data.SqlClient (integration tests and sample only) moved from 6.1.6 to 6.1.8, which takes System.Security.Cryptography.Pkcs 9.0.21 and clears CVE-2026-70162 and CVE-2026-84008. diff --git a/docs/storage-providers.md b/docs/storage-providers.md index 18df374..877ab88 100644 --- a/docs/storage-providers.md +++ b/docs/storage-providers.md @@ -39,8 +39,9 @@ protected override void OnModelCreating(ModelBuilder mb) // Options: SqlServer | PostgreSql | Sqlite ``` -> **Upgrading from `1.0.0-preview.1`?** `ISettingDbContext` is obsolete and does nothing. -> Delete `: ISettingDbContext` and the two `DbSet` properties; nothing else changes. +> **Upgrading from `1.0.0-preview.1`?** `ISettingDbContext` is gone. Delete +> `: ISettingDbContext` and the two `DbSet` properties; nothing else changes. If you call +> `ApplySettingsConfiguration`, rename it to `ApplySettings`. > `UseEntityFrameworkCore()` registers nothing against `DbContext` itself, so the > library never claims a framework type it does not own. diff --git a/src/DotNetBoost.Settings.EntityFrameworkCore/EfCore.cs b/src/DotNetBoost.Settings.EntityFrameworkCore/EfCore.cs index 05efcc6..92a422a 100644 --- a/src/DotNetBoost.Settings.EntityFrameworkCore/EfCore.cs +++ b/src/DotNetBoost.Settings.EntityFrameworkCore/EfCore.cs @@ -2,7 +2,6 @@ using DotNetBoost.Settings.Core.Interfaces; using DotNetBoost.Settings.Core.Models; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.Extensions.DependencyInjection; @@ -21,34 +20,6 @@ public enum DatabaseProvider Sqlite } -/// -/// No longer required. The EF Core store reads its entities through -/// , so a context needs neither this interface nor the -/// DbSet properties it declares — ApplySettings in OnModelCreating is the -/// whole registration. -/// -/// -/// Kept for one release so existing contexts keep compiling. Delete the -/// : ISettingDbContext and the DbSet property; nothing else changes. -/// -[Obsolete("ISettingDbContext is no longer used. Remove it and the DbSet properties from your " + - "DbContext; UseEntityFrameworkCore() only needs a DbContext. " + - "This interface will be removed in the next release.")] -public interface ISettingDbContext -{ - /// The persisted settings rows. - DbSet Settings { get; } - - /// Persists pending changes. - Task SaveChangesAsync(CancellationToken cancellationToken = default); - - /// - /// Change-tracking entry for an entity. Needed to state the concurrency token a write is - /// conditional on. A satisfies this member implicitly. - /// - EntityEntry Entry(TEntity entity) where TEntity : class; -} - /// Entity mapping for , tuned per engine. /// Selects the value column type and concurrency-token style. public sealed class SettingConfiguration(DatabaseProvider provider) @@ -121,23 +92,6 @@ public static ModelBuilder ApplySettings(this ModelBuilder mb, DbContext context return mb.ApplySettings(ResolveProvider(context.Database.ProviderName)); } - /// - /// Renamed to . - /// - /// - /// Kept for one release so existing contexts keep compiling, the same way - /// is. The old name described the mechanism rather than - /// the library, which read as meaningful next to the ApplyMedia() and - /// ApplyMultiLingual() calls it usually sits beside. It was not. - /// - [Obsolete(RenamedMessage)] - public static ModelBuilder ApplySettingsConfiguration(this ModelBuilder mb, DatabaseProvider provider) - => mb.ApplySettings(provider); - - private const string RenamedMessage = - "Renamed to ApplySettings, to match ApplyMedia and ApplyMultiLingual. " + - "This alias will be removed in the next release."; - /// /// Maps an EF Core provider assembly name onto the engine whose column types and /// concurrency-token style the settings tables need. diff --git a/tests/DotNetBoost.Settings.ProviderTests/Stores/EfCoreModelConfigurationTests.cs b/tests/DotNetBoost.Settings.ProviderTests/Stores/EfCoreModelConfigurationTests.cs index 5e16e20..cc22a8b 100644 --- a/tests/DotNetBoost.Settings.ProviderTests/Stores/EfCoreModelConfigurationTests.cs +++ b/tests/DotNetBoost.Settings.ProviderTests/Stores/EfCoreModelConfigurationTests.cs @@ -43,22 +43,6 @@ public void InferringOverload_OnAnUnmappedProvider_SaysWhatToDoInstead() Assert.Contains("Microsoft.EntityFrameworkCore.InMemory", Flatten(ex), StringComparison.Ordinal); } - /// - /// The whole purpose of the obsolete alias: a context written against the old name keeps - /// compiling and keeps building the same model. Deprecation warning, not a break. - /// - [Theory] - [InlineData(DatabaseProvider.SqlServer, "nvarchar(max)")] - [InlineData(DatabaseProvider.PostgreSql, "text")] - [InlineData(DatabaseProvider.Sqlite, "TEXT")] - public void TheObsoleteName_StillBuildsTheSameModel(DatabaseProvider provider, string expectedColumnType) - { - Assert.Equal(expectedColumnType, ValueColumnTypeOf(ObsoleteContextFor(provider))); - - // And the same as the new name produces, rather than merely something plausible. - Assert.Equal(ValueColumnTypeOf(ContextFor(provider)), ValueColumnTypeOf(ObsoleteContextFor(provider))); - } - /// EF wraps a throw from OnModelCreating, so assert over the whole chain. private static string Flatten(Exception ex) { @@ -117,35 +101,6 @@ protected override void OnModelCreating(ModelBuilder mb) => mb.ApplySettings(DatabaseProvider.Sqlite); } - // One context type per provider, as above, each calling the obsolete alias. CS0618 is - // suppressed here and nowhere else: these exist precisely to exercise the deprecated name. - private static DbContext ObsoleteContextFor(DatabaseProvider provider) => provider switch - { - DatabaseProvider.SqlServer => new ObsoleteSqlServerContext(), - DatabaseProvider.PostgreSql => new ObsoletePostgresContext(), - _ => new ObsoleteSqliteContext() - }; - -#pragma warning disable CS0618 // Deliberately calling the obsolete alias. - private sealed class ObsoleteSqlServerContext : SqliteBackedContext - { - protected override void OnModelCreating(ModelBuilder mb) - => mb.ApplySettingsConfiguration(DatabaseProvider.SqlServer); - } - - private sealed class ObsoletePostgresContext : SqliteBackedContext - { - protected override void OnModelCreating(ModelBuilder mb) - => mb.ApplySettingsConfiguration(DatabaseProvider.PostgreSql); - } - - private sealed class ObsoleteSqliteContext : SqliteBackedContext - { - protected override void OnModelCreating(ModelBuilder mb) - => mb.ApplySettingsConfiguration(DatabaseProvider.Sqlite); - } -#pragma warning restore CS0618 - private sealed class InMemoryContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder options)