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 a42b921068..2c705a01fd 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
@@ -10,8 +10,18 @@ public class AbpEntityChangeOptions
public IEntitySelectorList IgnoredNavigationEntitySelectors { get; set; }
+ ///
+ /// Default: true.
+ /// Update the aggregate root when any navigation property changes.
+ /// Some properties like ConcurrencyStamp,LastModificationTime,LastModifierId etc. will be updated.
+ ///
+ public bool UpdateAggregateRootWhenNavigationChanges { get; set; } = true;
+
+ public IEntitySelectorList IgnoredUpdateAggregateRootSelectors { get; set; }
+
public AbpEntityChangeOptions()
{
IgnoredNavigationEntitySelectors = new EntitySelectorList();
+ IgnoredUpdateAggregateRootSelectors = new EntitySelectorList();
}
}
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 626daf0bb4..e91d08b8c9 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
@@ -274,7 +274,9 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
continue;
}
- if (entityEntry.State == EntityState.Unchanged)
+ if (EntityChangeOptions.Value.UpdateAggregateRootWhenNavigationChanges &&
+ EntityChangeOptions.Value.IgnoredUpdateAggregateRootSelectors.All(selector => !selector.Predicate(entityEntry.Entity.GetType())) &&
+ entityEntry.State == EntityState.Unchanged)
{
ApplyAbpConceptsForModifiedEntity(entityEntry, true);
}
@@ -446,7 +448,12 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.All(selector => !selector.Predicate(entry.Entity.GetType())) &&
AbpEfCoreNavigationHelper.IsNavigationEntryModified(entry))
{
- ApplyAbpConceptsForModifiedEntity(entry, true);
+ if (EntityChangeOptions.Value.UpdateAggregateRootWhenNavigationChanges &&
+ EntityChangeOptions.Value.IgnoredUpdateAggregateRootSelectors.All(selector => !selector.Predicate(entry.Entity.GetType())))
+ {
+ ApplyAbpConceptsForModifiedEntity(entry, true);
+ }
+
if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
@@ -478,11 +485,13 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
}
}
- if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
+ if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges &&
+ EntityChangeOptions.Value.UpdateAggregateRootWhenNavigationChanges)
{
foreach (var entry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()
.Where(x => x.State == EntityState.Unchanged)
- .Where(x=> EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.All(selector => !selector.Predicate(x.Entity.GetType()))))
+ .Where(x => EntityChangeOptions.Value.IgnoredNavigationEntitySelectors.All(selector => !selector.Predicate(x.Entity.GetType())))
+ .Where(x => EntityChangeOptions.Value.IgnoredUpdateAggregateRootSelectors.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 d82fb195ab..6249d588f5 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
@@ -396,3 +396,134 @@ public class AbpEfCoreDomainEvents_Tests : EntityFrameworkCoreTestBase
entityUpdatedEventTriggered.ShouldBeTrue();
}
}
+
+
+public abstract class AbpEfCoreDomainEvents_Disable_UpdateAggregateRoot_Tests : EntityFrameworkCoreTestBase
+{
+ protected readonly IRepository AppEntityWithNavigationsRepository;
+ protected readonly IRepository AppEntityWithNavigationForeignRepository;
+ protected readonly ILocalEventBus LocalEventBus;
+
+ protected AbpEfCoreDomainEvents_Disable_UpdateAggregateRoot_Tests()
+ {
+ AppEntityWithNavigationsRepository = GetRequiredService>();
+ AppEntityWithNavigationForeignRepository = GetRequiredService>();
+ LocalEventBus = GetRequiredService();
+ }
+
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.PublishEntityUpdatedEventWhenNavigationChanges = true;
+ options.UpdateAggregateRootWhenNavigationChanges = false;
+ });
+
+ base.AfterAddApplication(services);
+ }
+
+ [Fact]
+ public async Task Should_Trigger_Domain_Events_But_Do_Not_Change_Aggregate_Root_When_Navigation_Changes_Tests()
+ {
+ var entityId = Guid.NewGuid();
+
+ var newEntity = await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity"));
+
+ var latestConcurrencyStamp = newEntity.ConcurrencyStamp;
+ var lastModificationTime = newEntity.LastModificationTime;
+
+ var entityUpdatedEventTriggered = false;
+
+ LocalEventBus.Subscribe>(data =>
+ {
+ entityUpdatedEventTriggered = true;
+
+ // The Aggregate will not be updated
+ data.Entity.ConcurrencyStamp.ShouldBe(latestConcurrencyStamp);
+ data.Entity.LastModificationTime.ShouldBe(lastModificationTime);
+ return Task.CompletedTask;
+ });
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+ }
+}
+
+public class AbpEfCoreDomainEvents_UpdateAggregateRootWhenNavigationChanges_Tests : AbpEfCoreDomainEvents_Disable_UpdateAggregateRoot_Tests
+{
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.UpdateAggregateRootWhenNavigationChanges = false;
+ });
+
+ base.AfterAddApplication(services);
+ }
+}
+
+public class AbpEfCoreDomainEvents_IgnoredUpdateAggregateRootSelectors_Test : AbpEfCoreDomainEvents_Disable_UpdateAggregateRoot_Tests
+{
+ protected override void AfterAddApplication(IServiceCollection services)
+ {
+ services.Configure(options =>
+ {
+ options.IgnoredUpdateAggregateRootSelectors.Add("AppEntityWithValueObjectAddress", x => x == typeof(AppEntityWithNavigations));
+ });
+
+ base.AfterAddApplication(services);
+ }
+}