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)