From 150282386ef8efc341dcf97789cabae52e7a26b7 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 23 May 2024 11:32:00 +0800 Subject: [PATCH] `PublishEntityUpdatedEvent` when foreign key and property are changed. Resolve #19895 --- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 21 +++- .../TestMigrationsDbContext.cs | 8 ++ .../EntityFrameworkCore/TestAppDbContext.cs | 8 ++ .../Domain/AppEntityWithNavigations.cs | 19 ++++ .../Abp/TestApp/Testing/DomainEvents_Tests.cs | 101 ++++++++++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) 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 638860e742..138bf82ec4 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -192,9 +192,9 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, { try { - if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) + foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()) { - foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()) + if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) { if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) { @@ -205,6 +205,23 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, 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); + } + } } var auditLog = AuditingManager?.Current?.Log; diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 225e767d26..0992a904ad 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -28,6 +28,8 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet AppEntityWithNavigations { get; set; } + public DbSet AppEntityWithNavigationsForeign { get; set; } + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -74,6 +76,7 @@ public class TestMigrationsDbContext : AbpDbContext b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); + b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithNavigationForeignId).IsRequired(false); }); modelBuilder.Entity(b => @@ -87,5 +90,10 @@ public class TestMigrationsDbContext : AbpDbContext b.ConfigureByConvention(); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index e668a21e26..fcaf8e5f7a 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -36,6 +36,8 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet AppEntityWithNavigations { get; set; } + public DbSet AppEntityWithNavigationsForeign { get; set; } + public TestAppDbContext(DbContextOptions options) : base(options) { @@ -101,6 +103,7 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); + b.HasOne().WithMany().HasForeignKey(x => x.AppEntityWithNavigationForeignId).IsRequired(false); }); modelBuilder.Entity(b => @@ -115,6 +118,11 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + }); + modelBuilder.TryConfigureObjectExtensions(); } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs index e9b9706e0c..2653948163 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs @@ -23,6 +23,7 @@ public class AppEntityWithNavigations : AggregateRoot public string FullName { get; set; } + public AppEntityWithValueObjectAddress AppEntityWithValueObjectAddress { get; set; } public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; } @@ -30,6 +31,8 @@ public class AppEntityWithNavigations : AggregateRoot public virtual List OneToMany { get; set; } public virtual List ManyToMany { get; set; } + + public virtual Guid? AppEntityWithNavigationForeignId { get; set; } } public class AppEntityWithValueObjectAddress : ValueObject @@ -88,3 +91,19 @@ public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany public Guid AppEntityWithNavigationChildManyToManyId { get; set; } } + +public class AppEntityWithNavigationsForeign : AggregateRoot +{ + protected AppEntityWithNavigationsForeign() + { + + } + + public AppEntityWithNavigationsForeign(Guid id, string name) + : base(id) + { + Name = name; + } + + public string Name { get; set; } +} 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 7db0ba180d..e5e3ad5cb6 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 @@ -191,11 +191,13 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests where TStartupModule : IAbpModule { protected readonly IRepository AppEntityWithNavigationsRepository; + protected readonly IRepository AppEntityWithNavigationForeignRepository; protected readonly ILocalEventBus LocalEventBus; protected AbpEntityChangeOptions_DomainEvents_Tests() { AppEntityWithNavigationsRepository = GetRequiredService>(); + AppEntityWithNavigationForeignRepository = GetRequiredService>(); LocalEventBus = GetRequiredService(); } @@ -288,4 +290,103 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests }); entityUpdatedEventTriggered.ShouldBeFalse(); } + + [Fact] + public async Task Should_Trigger_EntityUpdatedEvent_For_Aggregate_Root_When_Property_And_Navigation_Changes_Tests() + { + var entityId = Guid.NewGuid(); + await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity")); + + var entityWithNavigationForeignId = Guid.NewGuid(); + var entityWithNavigationForeignId2 = Guid.NewGuid(); + await AppEntityWithNavigationForeignRepository.InsertAsync(new AppEntityWithNavigationsForeign(entityWithNavigationForeignId, "TestEntityWithNavigationForeign")); + await AppEntityWithNavigationForeignRepository.InsertAsync(new AppEntityWithNavigationsForeign(entityWithNavigationForeignId2, "TestEntityWithNavigationForeign2")); + + var entityUpdatedEventTriggered = false; + + LocalEventBus.Subscribe>(data => + { + entityUpdatedEventTriggered = !entityUpdatedEventTriggered; + return Task.CompletedTask; + }); + + // Test with simple property with foreign key + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + entity.AppEntityWithNavigationForeignId = entityWithNavigationForeignId; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test only foreign key changed + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.AppEntityWithNavigationForeignId = entityWithNavigationForeignId2; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeFalse(); + + // Test with simple property with value object + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey"); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with simple property with one to one + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + entity.OneToOne = new AppEntityWithNavigationChildOneToOne + { + ChildName = "ChildName" + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with simple property with one to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + entity.OneToMany = new List() + { + new AppEntityWithNavigationChildOneToMany + { + AppEntityWithNavigationId = entity.Id, + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with simple property with many to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + entity.ManyToMany = new List() + { + new AppEntityWithNavigationChildManyToMany + { + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + } }