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 1/5] 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