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 f350d0c614..41b39ddea9 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -274,14 +274,14 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e) { - AbpEfCoreNavigationHelper.ChangeTracker_Tracked(ChangeTracker, sender, e); + AbpEfCoreNavigationHelper.ChangeTracker_Tracked(sender, e); FillExtraPropertiesForTrackedEntities(e); PublishEventsForTrackedEntity(e.Entry); } protected virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e) { - AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(ChangeTracker, sender, e); + AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(sender, e); PublishEventsForTrackedEntity(e.Entry); } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs index 6ee01fdd8e..4a57f0803c 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; @@ -20,16 +17,16 @@ public class AbpEfCoreNavigationHelper : ITransientDependency { protected Dictionary EntityEntries { get; } = new(); - public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e) + public virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e) { EntityEntryTrackedOrStateChanged(e.Entry); - DetectChanges(changeTracker, e.Entry); + DetectChanges(e.Entry); } - public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e) + public virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e) { EntityEntryTrackedOrStateChanged(e.Entry); - DetectChanges(changeTracker, e.Entry); + DetectChanges(e.Entry); } protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) @@ -53,7 +50,7 @@ public class AbpEfCoreNavigationHelper : ITransientDependency EntityEntries.Add(entryId, new AbpEntityEntry(entryId, entityEntry)); } - protected virtual void DetectChanges(ChangeTracker changeTracker, EntityEntry entityEntry) + protected virtual void DetectChanges(EntityEntry entityEntry) { if (entityEntry.State != EntityState.Added && entityEntry.State != EntityState.Deleted && @@ -62,8 +59,13 @@ public class AbpEfCoreNavigationHelper : ITransientDependency return; } -#pragma warning disable EF1001 - var stateManager = changeTracker.Context.GetDependencies().StateManager; + RecursiveDetectChanges(entityEntry); + } + + protected virtual void RecursiveDetectChanges(EntityEntry entityEntry) + { + #pragma warning disable EF1001 + var stateManager = entityEntry.Context.GetDependencies().StateManager; var internalEntityEntityEntry = stateManager.Entries.FirstOrDefault(x => x.Entity == entityEntry.Entity); if (internalEntityEntityEntry == null) { @@ -85,7 +87,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency continue; } - abpEntityEntry.IsModified = true; + if (!abpEntityEntry.IsModified) + { + abpEntityEntry.IsModified = true; + RecursiveDetectChanges(abpEntityEntry.EntityEntry); + } + var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ?? abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey); if (navigationEntry != null) @@ -119,7 +126,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency continue; } - abpEntityEntry.IsModified = true; + if (!abpEntityEntry.IsModified) + { + abpEntityEntry.IsModified = true; + RecursiveDetectChanges(abpEntityEntry.EntityEntry); + } + var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ?? abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey); if (navigationEntry != null) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs index c26ae2bbf1..0aef48e0d1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; @@ -17,7 +18,7 @@ public class AbpEntityEntry { get { - return _isModified || NavigationEntries.Any(n => n.IsModified); + return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified); } set { 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 428e0239c3..98364788b3 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 @@ -51,7 +51,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule options.Entity(opt => { - opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany); + opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).ThenInclude(x => x.OneToOne).Include(p => p.OneToMany).ThenInclude(x => x.OneToMany).Include(p => p.ManyToMany); }); }); 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 5de4bd6369..225e767d26 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 @@ -70,10 +70,22 @@ public class TestMigrationsDbContext : AbpDbContext modelBuilder.Entity(b => { b.ConfigureByConvention(); - b.OwnsOne(v => v.AppEntityWithValueObjectAddress); + b.OwnsOne(x => x.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(x => x.ManyToMany).UsingEntity(); }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId); + }); } } 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 63b57ec147..e668a21e26 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 @@ -103,6 +103,18 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); }); + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); + }); + + modelBuilder.Entity(b => + { + b.ConfigureByConvention(); + b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId); + }); + 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 b0c2e4f7bf..e9b9706e0c 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 @@ -36,7 +36,6 @@ public class AppEntityWithValueObjectAddress : ValueObject { public AppEntityWithValueObjectAddress(string country) { - Country = country; } @@ -48,8 +47,14 @@ public class AppEntityWithValueObjectAddress : ValueObject } } - public class AppEntityWithNavigationChildOneToOne : Entity +{ + public string ChildName { get; set; } + + public virtual AppEntityWithNavigationChildOneToOneAndOneToOne OneToOne { get; set; } +} + +public class AppEntityWithNavigationChildOneToOneAndOneToOne : Entity { public string ChildName { get; set; } } @@ -59,6 +64,15 @@ public class AppEntityWithNavigationChildOneToMany : Entity public Guid AppEntityWithNavigationId { get; set; } public string ChildName { get; set; } + + public virtual List OneToMany { get; set; } +} + +public class AppEntityWithNavigationChildOneToManyAndOneToMany : Entity +{ + public Guid AppEntityWithNavigationChildOneToManyId { get; set; } + + public string ChildName { get; set; } } public class AppEntityWithNavigationChildManyToMany : AggregateRoot 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 ddb91e7ba6..c24d4567f4 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 @@ -252,7 +252,11 @@ public abstract class DomainEvents_Tests : TestAppTestBase : TestAppTestBase + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToOne.OneToOne.ChildName = "OneToOne-ChildName2"; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount); + LocalEventBus.Subscribe>(data => { throw new Exception("Should not trigger this event"); @@ -294,7 +308,14 @@ public abstract class DomainEvents_Tests : TestAppTestBase() + { + new AppEntityWithNavigationChildOneToManyAndOneToMany() + { + ChildName = "OneToMany-ChildName1" + } + } } }; await AppEntityWithNavigationsRepository.UpdateAsync(entity); @@ -312,6 +333,16 @@ public abstract class DomainEvents_Tests : TestAppTestBase + { + var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId); + entity.OneToMany[0].OneToMany[0].ChildName = "OneToMany-ChildName2"; + await AppEntityWithNavigationsRepository.UpdateAsync(entity); + }); + entityUpdatedEventTriggered.ShouldBeTrue(); + personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount); + LocalEventBus.Subscribe>(data => { throw new Exception("Should not trigger this event");