From 6c8d37058d7a4fe7edfa8401e195b24fe76d7241 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 10 Mar 2025 10:16:32 +0800 Subject: [PATCH] Add `IgnoredNavigationEntitySelectors` to `AbpEntityChangeOptions` to ignore navigation properties. --- .../Entities/Events/AbpEntityChangeOptions.cs | 7 ++ .../Entities/Events/EntitySelectorList.cs | 8 ++ .../Events/EntitySelectorListExtensions.cs | 12 +++ .../Entities/Events/IEntitySelectorList.cs | 8 ++ .../Abp/EntityFrameworkCore/AbpDbContext.cs | 96 +++++++++++-------- .../DomainEvents/DomainEvents_Tests.cs | 25 ++++- .../Abp/TestApp/Testing/DomainEvents_Tests.cs | 10 -- .../AbpOpenIddictEntityFrameworkCoreModule.cs | 6 ++ 8 files changed, 122 insertions(+), 50 deletions(-) create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorList.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorListExtensions.cs create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/IEntitySelectorList.cs diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs index 0d8d74f7b2..a42b921068 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs @@ -7,4 +7,11 @@ public class AbpEntityChangeOptions /// Publish the EntityUpdatedEvent when any navigation property changes. /// public bool PublishEntityUpdatedEventWhenNavigationChanges { get; set; } = true; + + public IEntitySelectorList IgnoredNavigationEntitySelectors { get; set; } + + public AbpEntityChangeOptions() + { + IgnoredNavigationEntitySelectors = new EntitySelectorList(); + } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorList.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorList.cs new file mode 100644 index 0000000000..f0c89979f9 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Domain.Entities.Events; + +internal class EntitySelectorList : List, IEntitySelectorList +{ + +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorListExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorListExtensions.cs new file mode 100644 index 0000000000..a05aaa616a --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntitySelectorListExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Domain.Entities.Events; + +public static class EntitySelectorListExtensions +{ + public static IEntitySelectorList Add(this IEntitySelectorList selectors, string name, Func predicate) + { + selectors.Add(new NamedTypeSelector(name, predicate)); + return selectors; + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/IEntitySelectorList.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/IEntitySelectorList.cs new file mode 100644 index 0000000000..b3ed916dee --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/IEntitySelectorList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Domain.Entities.Events; + +public interface IEntitySelectorList : IList +{ + +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index e9bf710d7d..626daf0bb4 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -202,46 +202,11 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, } } - public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) + public async override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { try { - foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()) - { - if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) - { - if (entityEntry.State == EntityState.Unchanged) - { - ApplyAbpConceptsForModifiedEntity(entityEntry, true); - } - - if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) - { - EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); - } - else - { - EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); - } - } - else if (entityEntry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd))) - { - if (entityEntry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey())) - { - // Skip `PublishEntityDeletedEvent/PublishEntityUpdatedEvent` if only foreign keys have changed. - break; - } - - if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) - { - EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); - } - else - { - EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); - } - } - } + await PublishEventsForChangedEntityOnSaveChangeAsync(); var auditLog = AuditingManager?.Current?.Log; List? entityChangeList = null; @@ -296,6 +261,55 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, } } + protected virtual Task PublishEventsForChangedEntityOnSaveChangeAsync() + { + foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()) + { + if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) + { + var ignoredEntity = EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.Any(selector => selector.Predicate(entityEntry.Entity.GetType())); + var onlyForeignKeyModifiedEntity = entityEntry.State == EntityState.Modified && entityEntry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey()); + if ((entityEntry.State == EntityState.Unchanged && ignoredEntity) || onlyForeignKeyModifiedEntity && ignoredEntity) + { + continue; + } + + if (entityEntry.State == EntityState.Unchanged) + { + ApplyAbpConceptsForModifiedEntity(entityEntry, true); + } + + if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) + { + EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); + } + else + { + EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); + } + } + else if (entityEntry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd))) + { + if (entityEntry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey())) + { + // Skip `PublishEntityDeletedEvent/PublishEntityUpdatedEvent` if only foreign keys have changed. + break; + } + + if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) + { + EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); + } + else + { + EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); + } + } + } + + return Task.CompletedTask; + } + protected virtual void PublishEntityEvents(EntityEventReport changeReport) { foreach (var localEvent in changeReport.DomainEvents) @@ -428,7 +442,9 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); } } - else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && AbpEfCoreNavigationHelper.IsNavigationEntryModified(entry)) + else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && + EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.All(selector => !selector.Predicate(entry.Entity.GetType())) && + AbpEfCoreNavigationHelper.IsNavigationEntryModified(entry)) { ApplyAbpConceptsForModifiedEntity(entry, true); if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) @@ -464,7 +480,9 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) { - foreach (var entry in AbpEfCoreNavigationHelper.GetChangedEntityEntries().Where(x => x.State == EntityState.Unchanged)) + foreach (var entry in AbpEfCoreNavigationHelper.GetChangedEntityEntries() + .Where(x => x.State == EntityState.Unchanged) + .Where(x=> EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.All(selector => !selector.Predicate(x.Entity.GetType())))) { UpdateConcurrencyStamp(entry); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs index f1b072c0ee..d82fb195ab 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs @@ -18,8 +18,31 @@ public class DomainEvents_Tests : DomainEvents_Tests +public class AbpEntityChangeOptions_DomainEvents_PublishEntityUpdatedEventWhenNavigationChanges_Tests : AbpEntityChangeOptions_DomainEvents_Tests { + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.PublishEntityUpdatedEventWhenNavigationChanges = false; + }); + + base.AfterAddApplication(services); + } +} + +public class AbpEntityChangeOptions_DomainEvents_IgnoreEntityChangeSelectorList_Tests : AbpEntityChangeOptions_DomainEvents_Tests +{ + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.PublishEntityUpdatedEventWhenNavigationChanges = true; + options.IgnoredNavigationEntitySelectors.Add("DisableAllEntity", _ => true); + }); + + base.AfterAddApplication(services); + } } public class AbpEfCoreDomainEvents_Tests : EntityFrameworkCoreTestBase diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs index 8ac64d40c9..032d714390 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs @@ -259,16 +259,6 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests LocalEventBus = GetRequiredService(); } - protected override void AfterAddApplication(IServiceCollection services) - { - services.Configure(options => - { - options.PublishEntityUpdatedEventWhenNavigationChanges = false; - }); - - base.AfterAddApplication(services); - } - [Fact] public async Task Should_Not_Trigger_Domain_Events_For_Aggregate_Root_When_Navigation_Changes_Tests() { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.EntityFrameworkCore/Volo/Abp/OpenIddict/EntityFrameworkCore/AbpOpenIddictEntityFrameworkCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.EntityFrameworkCore/Volo/Abp/OpenIddict/EntityFrameworkCore/AbpOpenIddictEntityFrameworkCoreModule.cs index 9ef08d5f2e..a12f4161c5 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.EntityFrameworkCore/Volo/Abp/OpenIddict/EntityFrameworkCore/AbpOpenIddictEntityFrameworkCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.EntityFrameworkCore/Volo/Abp/OpenIddict/EntityFrameworkCore/AbpOpenIddictEntityFrameworkCoreModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict.Applications; @@ -25,5 +26,10 @@ public class AbpOpenIddictEntityFrameworkCoreModule : AbpModule options.AddRepository(); options.AddRepository(); }); + + Configure(options => + { + options.IgnoredNavigationEntitySelectors.Add("DisableOpenIddictApplication", type => type == typeof(OpenIddictApplication)); + }); } }