From 340bd51f490943eb19bc13d47c654eb6d1b6a083 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 14 May 2026 09:12:52 +0800 Subject: [PATCH] Index batch state checker models by state for O(1) lookup - Mirror _models with Dictionary populated in AddCheckModels - GetModelOrNull goes through the dict, matching IsEnabledAsync's first-wins - Pin first-wins via a regression test --- ...RequirePermissionsSimpleBatchStateChecker.cs | 12 +++++++++++- .../RequireFeaturesSimpleBatchStateChecker.cs | 12 +++++++++++- ...uireFeaturesSimpleBatchStateChecker_Tests.cs | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs index 3fdf76a9d2..d78e0ceb6f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs @@ -27,9 +27,12 @@ public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStat private readonly List> _models; + private readonly Dictionary> _modelsByState; + public RequirePermissionsSimpleBatchStateChecker() { _models = new List>(); + _modelsByState = new Dictionary>(); } public RequirePermissionsSimpleBatchStateChecker AddCheckModels(params RequirePermissionsSimpleBatchStateCheckerModel[] models) @@ -37,6 +40,13 @@ public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStat Check.NotNullOrEmpty(models, nameof(models)); _models.AddRange(models); + foreach (var model in models) + { + if (!_modelsByState.ContainsKey(model.State)) + { + _modelsByState[model.State] = model; + } + } return this; } @@ -49,7 +59,7 @@ public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStat public virtual RequirePermissionsSimpleBatchStateCheckerModel? GetModelOrNull(TState state) { - return _models.FirstOrDefault(m => EqualityComparer.Default.Equals(m.State, state)); + return _modelsByState.TryGetValue(state, out var model) ? model : null; } public override async Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs index 64ad3df37a..5faf1b891a 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs @@ -27,9 +27,12 @@ public class RequireFeaturesSimpleBatchStateChecker : SimpleBatchStateCh private readonly List> _models; + private readonly Dictionary> _modelsByState; + public RequireFeaturesSimpleBatchStateChecker() { _models = new List>(); + _modelsByState = new Dictionary>(); } public RequireFeaturesSimpleBatchStateChecker AddCheckModels( @@ -38,6 +41,13 @@ public class RequireFeaturesSimpleBatchStateChecker : SimpleBatchStateCh Check.NotNullOrEmpty(models, nameof(models)); _models.AddRange(models); + foreach (var model in models) + { + if (!_modelsByState.ContainsKey(model.State)) + { + _modelsByState[model.State] = model; + } + } return this; } @@ -50,7 +60,7 @@ public class RequireFeaturesSimpleBatchStateChecker : SimpleBatchStateCh public virtual RequireFeaturesSimpleBatchStateCheckerModel? GetModelOrNull(TState state) { - return _models.FirstOrDefault(x => EqualityComparer.Default.Equals(x.State, state)); + return _modelsByState.TryGetValue(state, out var model) ? model : null; } public override async Task> IsEnabledAsync( diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs index 3e26235672..acd961931e 100644 --- a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs @@ -85,6 +85,23 @@ public class RequireFeaturesSimpleBatchStateChecker_Tests : FeatureTestBase } } + [Fact] + public void GetModelOrNull_Returns_First_Win_When_Same_State_Registered_Twice() + { + // Mirrors IsEnabledAsync's modelLookup behaviour: when the same state is registered + // multiple times, the first registration wins. Backed by the dictionary index. + + var checker = new RequireFeaturesSimpleBatchStateChecker(); + var state = new NamedState("A"); + + checker.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(state, new[] { "First" }, true)); + checker.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(state, new[] { "Second" }, true)); + + checker.GetModelOrNull(state)!.FeatureNames.ShouldBe(new[] { "First" }); + } + [Fact] public void GetModelOrNull_Uses_Same_Equality_As_Runtime() {