Open Closed

Global settings unique index doesn't prevent duplicates when ProviderKey is NULL (SQL Server) #10892


User avatar
0
kberntsen created

Issue reported against the framework in gh repo

https://github.com/abpframework/abp/issues/26208

Bug: Global settings unique index doesn't prevent duplicates when ProviderKey is NULL (SQL Server)

Summary

SettingManagementDbContextModelBuilderExtensions.ConfigureSettingManagement defines a unique index on AbpSettings(Name, ProviderName, ProviderKey):

b.HasIndex(x => new { x.Name, x.ProviderName, x.ProviderKey }).IsUnique(true);

For tenant-scoped settings ( ProviderKey = <tenantId> ), this correctly prevents duplicate
rows. For global/host-scoped settings ( ProviderName = "G" ,  ProviderKey = NULL ), the
constraint silently fails to prevent duplicates on SQL Server, because SQL Server (like most
RDBMS unique-index implementations) treats each  NULL  as distinct from every other  NULL  for
uniqueness purposes — i.e.  (Name='Foo', ProviderName='G', ProviderKey=NULL)  and a second
identical row do not violate the unique constraint.

 SettingManager.SetGlobalAsync / SetForTenantAsync  performs a check-then-insert rather than an
atomic upsert. Under concurrent calls (e.g. two near-simultaneous requests that both persist the
same global setting before either commit), this produces two rows with the same
 (Name, ProviderName, ProviderKey=NULL)  and no DB-level guard stops it.

Impact

Once two duplicate global setting rows exist, every subsequent read of that setting throws,
because  SettingManagementStore.SetCacheItemsAsync  builds the settings cache via:

settingValues.ToDictionary(x => x.Name, x => x.Value)

 ToDictionary  throws  ArgumentException: An item with the same key has already been added  on
the duplicate  Name . This is a hard, unrecoverable 500 on every request that reads that
setting's cache — in our case it broke  /Account/Login  for the entire application (through
 ExternalProviderSettingsHelper.GetAllForHostAsync()  →  SettingManager.GetOrNullAsync  →
 SettingManagementStore.GetCacheItemAsync ), until the duplicate row was manually deleted from
the database.

Repro

1. Trigger two concurrent calls to  ISettingManager.SetGlobalAsync("MyModule.MySetting", value) 
for a setting that does not yet have a global row (e.g. two app-service requests racing on
first save, or a distributed deployment with two pods handling near-simultaneous requests).
2. Both calls read "no existing row", then both insert.
3. Query  AbpSettings  — two rows exist with identical  Name / ProviderName='G' / ProviderKey=NULL .
4. Any subsequent call to read that setting (directly or via any code path that populates the
settings cache for that provider/providerKey) throws  ArgumentException  and surfaces as a 500.

Environment

• ABP Framework (Volo.Abp.SettingManagement / Volo.Abp.SettingManagement.EntityFrameworkCore) 10.6.0
• SQL Server (Azure SQL), EF Core provider
• .NET 10

Suggested Fix

Add a filtered unique index for the null- ProviderKey  case (SQL Server  HasFilter ), in addition
to the existing index, e.g.:

b.HasIndex(x => new { x.Name, x.ProviderName })
    .IsUnique()
    .HasFilter("[ProviderKey] IS NULL");

Alternatively/additionally,  SettingManager.SetGlobalAsync / SetForTenantAsync  could perform an
atomic upsert (e.g.  MERGE , or catch the unique-constraint violation and retry as an update)
rather than check-then-insert, to close the race condition at its source rather than only at the
DB-constraint level.

Workaround (applied)

Manually deleted the duplicate row from  AbpSettings  to unblock login. Adding an app-level
filtered unique index migration (scoped to our own DbContext) as defense-in-depth until this is
fixed upstream or we adopt the fix ourselves.
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

No answer yet!
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 23, 2026, 09:57
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.