From bae8008bebd27b56a0dd02b5ab3ac6c290434e90 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:12:41 +0800 Subject: [PATCH] Publish `EntityUpdatedEvent` when navigation changes. --- .../Entities/Events/AbpEntityChangeOptions.cs | 10 + .../Abp/EntityFrameworkCore/AbpDbContext.cs | 48 +++- .../Volo/Abp/Testing/Utils/ITestCounter.cs | 2 + .../Volo/Abp/Testing/Utils/TestCounter.cs | 8 + .../Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs | 1 + .../Volo/Abp/Uow/UnitOfWorkEventRecord.cs | 7 +- .../AbpEntityFrameworkCoreTestModule.cs | 5 + .../DomainEvents/DomainEvents_Tests.cs | 4 + .../TestMigrationsDbContext.cs | 11 + .../EntityFrameworkCore/TestAppDbContext.cs | 11 + .../MemoryDb/TestAppMemoryDbContext.cs | 3 +- .../Serializer/MongoDB_DateTimeKind_Tests.cs | 7 +- .../TestApp/MongoDb/ITestAppMongoDbContext.cs | 2 + .../TestApp/MongoDb/TestAppMongoDbContext.cs | 4 +- .../Domain/AppEntityWithNavigations.cs | 67 ++++++ .../Abp/TestApp/Testing/DomainEvents_Tests.cs | 227 +++++++++++++++++- .../Distributed_User_Change_Event_Tests.cs | 3 + 17 files changed, 408 insertions(+), 12 deletions(-) create mode 100644 framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.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 new file mode 100644 index 0000000000..0d8d74f7b2 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/AbpEntityChangeOptions.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.Domain.Entities.Events; + +public class AbpEntityChangeOptions +{ + /// + /// Default: true. + /// Publish the EntityUpdatedEvent when any navigation property changes. + /// + public bool PublishEntityUpdatedEventWhenNavigationChanges { get; set; } = true; +} 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 05dd4527fc..c8e88d7934 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -53,6 +53,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService(NullEntityChangeEventHelper.Instance); + public IOptions EntityChangeOptions => LazyServiceProvider.LazyGetRequiredService>(); + public IAuditPropertySetter AuditPropertySetter => LazyServiceProvider.LazyGetRequiredService(); public IEntityHistoryHelper EntityHistoryHelper => LazyServiceProvider.LazyGetService(NullEntityHistoryHelper.Instance); @@ -306,14 +308,15 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, } } - private void PublishEventsForTrackedEntity(EntityEntry entry) + protected virtual void PublishEventsForTrackedEntity(EntityEntry entry) { - switch (entry.State) + switch (entry.State) { case EntityState.Added: ApplyAbpConceptsForAddedEntity(entry); EntityChangeEventHelper.PublishEntityCreatedEvent(entry.Entity); break; + case EntityState.Modified: ApplyAbpConceptsForModifiedEntity(entry); if (entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) @@ -327,13 +330,52 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); } } - break; + case EntityState.Deleted: ApplyAbpConceptsForDeletedEntity(entry); EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity); break; } + + foreach (var entityEntry in new[] { entry }.Where(HasEntityEntryChanged).Concat(ChangeTracker.Entries().Where(HasEntityEntryChanged)).DistinctBy(x => x.Entity)) + { + ApplyAbpConceptsForModifiedEntity(entry); + + if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) + { + EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); + } + else + { + EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); + } + } + } + + protected virtual bool HasEntityEntryChanged(EntityEntry entry) + { + if (entry.State != EntityState.Modified && entry.State != EntityState.Unchanged) + { + return false; + } + + var changed = entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)); + if (changed) + { + return true; + } + + if (!changed && + EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) + { + if (entry.Navigations.Any(navigation => navigation.IsModified || (navigation is ReferenceEntry && navigation.As().TargetEntry?.State == EntityState.Modified))) + { + changed = true; + } + } + + return changed; } protected virtual void HandlePropertiesBeforeSave() diff --git a/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/ITestCounter.cs b/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/ITestCounter.cs index ec9ab4129a..0c6750f2bd 100644 --- a/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/ITestCounter.cs +++ b/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/ITestCounter.cs @@ -9,4 +9,6 @@ public interface ITestCounter int Increment(string name); int GetValue(string name); + + void ResetCount(string name); } diff --git a/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/TestCounter.cs b/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/TestCounter.cs index ae812f85f6..1a364abf81 100644 --- a/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/TestCounter.cs +++ b/framework/src/Volo.Abp.TestBase/Volo/Abp/Testing/Utils/TestCounter.cs @@ -39,4 +39,12 @@ public class TestCounter : ITestCounter, ISingletonDependency return _values.GetOrDefault(name); } } + + public void ResetCount(string name) + { + lock (_values) + { + _values[name] = 0; + } + } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index 74b1b79e77..609f5501a8 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -272,6 +272,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency } else { + eventRecord.SetOrder(eventRecords[foundIndex].EventOrder); eventRecords[foundIndex] = eventRecord; } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs index 303f3f890c..7b55618c48 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs @@ -9,7 +9,7 @@ public class UnitOfWorkEventRecord public Type EventType { get; } - public long EventOrder { get; } + public long EventOrder { get; protected set; } public bool UseOutbox { get; } @@ -29,4 +29,9 @@ public class UnitOfWorkEventRecord EventOrder = eventOrder; UseOutbox = useOutbox; } + + public void SetOrder(long order) + { + EventOrder = order; + } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index f23ac14fda..428e0239c3 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -48,6 +48,11 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule { opt.DefaultWithDetailsFunc = q => q.Include(p => p.Books); }); + + options.Entity(opt => + { + opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany); + }); }); context.Services.AddAbpDbContext(options => 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 da851d0a2c..cb7a5522b1 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 @@ -5,3 +5,7 @@ namespace Volo.Abp.EntityFrameworkCore.DomainEvents; public class DomainEvents_Tests : DomainEvents_Tests { } + +public class AbpEntityChangeOptions_DomainEvents_Tests : AbpEntityChangeOptions_DomainEvents_Tests +{ +} 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 2185bce2d4..702a9f34c7 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 @@ -26,6 +26,8 @@ public class TestMigrationsDbContext : AbpDbContext public DbSet Categories { get; set; } + public DbSet AppEntityWithNavigations { get; set; } + public TestMigrationsDbContext(DbContextOptions options) : base(options) { @@ -64,5 +66,14 @@ public class TestMigrationsDbContext : AbpDbContext { b.HasAbpQueryFilter(e => e.Name.StartsWith("abp")); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.OwnsOne(v => v.AppEntityWithValueObjectAddress); + 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(); + }); } } 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 245867776b..568c711f89 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 @@ -34,6 +34,8 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, public DbSet Categories { get; set; } + public DbSet AppEntityWithNavigations { get; set; } + public TestAppDbContext(DbContextOptions options) : base(options) { @@ -92,6 +94,15 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.HasAbpQueryFilter(e => e.Name.StartsWith("abp")); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.OwnsOne(v => v.AppEntityWithValueObjectAddress); + 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(); + }); + modelBuilder.TryConfigureObjectExtensions(); } } diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs index 4336889b90..73d0059cf4 100644 --- a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs @@ -11,7 +11,8 @@ public class TestAppMemoryDbContext : MemoryDbContext private static readonly Type[] EntityTypeList = { typeof(Person), typeof(EntityWithIntPk), - typeof(Product) + typeof(Product), + typeof(AppEntityWithNavigations) }; public override IReadOnlyList GetEntityTypes() diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs index 11277c1b74..6c1718f9a1 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Serializer/MongoDB_DateTimeKind_Tests.cs @@ -21,9 +21,6 @@ public abstract class MongoDB_DateTimeKind_Tests : DateTimeKind_Tests Cities { get; } IMongoCollection Products { get; } + + IMongoCollection AppEntityWithNavigations { get; } } diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs index 9bc2606878..4d6d439c02 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs @@ -24,9 +24,11 @@ public class TestAppMongoDbContext : AbpMongoDbContext, ITestAppMongoDbContext, public IMongoCollection DummyEntities => Collection(); public IMongoCollection FourthDummyEntities => Collection(); - + public IMongoCollection Products => Collection(); + public IMongoCollection AppEntityWithNavigations => Collection(); + protected internal override void CreateModel(IMongoModelBuilder modelBuilder) { base.CreateModel(modelBuilder); 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 new file mode 100644 index 0000000000..bde51a3ddb --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Values; + +namespace Volo.Abp.TestApp.Domain; + +public class AppEntityWithNavigations : AggregateRoot +{ + protected AppEntityWithNavigations() + { + + } + + public AppEntityWithNavigations(Guid id, string name) + : base(id) + { + Name = name; + FullName = name; + } + + public string Name { get; set; } + + public string FullName { get; set; } + + public AppEntityWithValueObjectAddress AppEntityWithValueObjectAddress { get; set; } + + public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; } + + public virtual List OneToMany { get; set; } + + public virtual List ManyToMany { get; set; } +} + +public class AppEntityWithValueObjectAddress : ValueObject +{ + public AppEntityWithValueObjectAddress(string country) + { + + Country = country; + } + + public string Country { get; set; } + + protected override IEnumerable GetAtomicValues() + { + yield return Country; + } +} + + +public class AppEntityWithNavigationChildOneToOne : Entity +{ + public string ChildName { get; set; } +} + +public class AppEntityWithNavigationChildOneToMany : Entity +{ + public Guid AppEntityWithNavigationId { get; set; } + + public string ChildName { get; set; } +} + +public class AppEntityWithNavigationChildManyToMany : Entity +{ + public string ChildName { 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 3bef65d544..2f94026764 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 @@ -1,6 +1,7 @@ using System; -using System.Linq; +using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Domain.Entities.Events; using Volo.Abp.Domain.Repositories; @@ -17,12 +18,14 @@ public abstract class DomainEvents_Tests : TestAppTestBase PersonRepository; + protected readonly IRepository AppEntityWithNavigationsRepository; protected readonly ILocalEventBus LocalEventBus; protected readonly IDistributedEventBus DistributedEventBus; protected DomainEvents_Tests() { PersonRepository = GetRequiredService>(); + AppEntityWithNavigationsRepository = GetRequiredService>(); LocalEventBus = GetRequiredService(); DistributedEventBus = GetRequiredService(); } @@ -173,6 +176,125 @@ public abstract class DomainEvents_Tests : TestAppTestBase>(data => + { + entityUpdatedEventTriggered = !entityUpdatedEventTriggered; + return Task.CompletedTask; + }); + + // Test with simple property + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with value object + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey"); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.AppEntityWithValueObjectAddress = null; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with one to one + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToOne = new AppEntityWithNavigationChildOneToOne + { + ChildName = "ChildName" + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToOne = null; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + // https://github.com/dotnet/efcore/issues/24076#issuecomment-1996623874 + // entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with one to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToMany = new List() + { + new AppEntityWithNavigationChildOneToMany + { + AppEntityWithNavigationId = entity.Id, + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToMany.Clear(); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + // https://github.com/dotnet/efcore/issues/24076#issuecomment-1996623874 + // entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with many to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.ManyToMany = new List() + { + new AppEntityWithNavigationChildManyToMany + { + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.ManyToMany.Clear(); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + } + private class MyCustomEventData { public string Value { get; set; } @@ -183,3 +305,106 @@ public abstract class DomainEvents_Tests : TestAppTestBase : TestAppTestBase + where TStartupModule : IAbpModule +{ + protected readonly IRepository AppEntityWithNavigationsRepository; + protected readonly ILocalEventBus LocalEventBus; + + protected AbpEntityChangeOptions_DomainEvents_Tests() + { + AppEntityWithNavigationsRepository = GetRequiredService>(); + 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() + { + var entityId = Guid.NewGuid(); + await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity")); + + var entityUpdatedEventTriggered = false; + + LocalEventBus.Subscribe>(data => + { + entityUpdatedEventTriggered = !entityUpdatedEventTriggered; + return Task.CompletedTask; + }); + + // Test with simple property + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.Name = Guid.NewGuid().ToString(); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + + // Test with value object + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey"); + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeFalse(); + + // Test with one to one + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToOne = new AppEntityWithNavigationChildOneToOne + { + ChildName = "ChildName" + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeFalse(); + + // Test with one to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToMany = new List() + { + new AppEntityWithNavigationChildOneToMany + { + AppEntityWithNavigationId = entity.Id, + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeFalse(); + + // Test with many to many + entityUpdatedEventTriggered = false; + await WithUnitOfWorkAsync(async () => + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.ManyToMany = new List() + { + new AppEntityWithNavigationChildManyToMany + { + ChildName = "ChildName1" + } + }; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeFalse(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs index 3cbbcc4f8e..b7703f3d21 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/Distributed_User_Change_Event_Tests.cs @@ -45,8 +45,11 @@ public class Distributed_User_Change_Event_Tests : AbpIdentityDomainTestBase [Fact] public async Task Should_Trigger_Distributed_EntityUpdated_Event() { + _testCounter.ResetCount("EntityUpdatedEto"); using (var uow = _unitOfWorkManager.Begin()) { + _testCounter.GetValue("EntityUpdatedEto").ShouldBe(0); + var user = await _userRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName("john.nash")); await _userManager.SetEmailAsync(user, "john.nash_UPDATED@abp.io");