From a2dc0a19a44e7d89914dbc216ad8993ecbe56836 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 15 Apr 2024 16:27:20 +0800 Subject: [PATCH] Optimize the performance of `AbpEfCoreNavigationHelper`. --- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 15 +- .../AbpEfCoreNavigationHelper.cs | 132 +++++++++++------- .../AbpEntityEntryNavigationProperty.cs | 10 +- .../EntityHistory/EntityHistoryHelper.cs | 5 +- 4 files changed, 107 insertions(+), 55 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 caab5aff944..ed7e4ea0508 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -181,6 +181,7 @@ public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, if (entityChangeList != null) { EntityHistoryHelper.UpdateChangeList(entityChangeList); + AbpEfCoreNavigationHelper.Clear(); auditLog!.EntityChanges.AddRange(entityChangeList); Logger.LogDebug($"Added {entityChangeList.Count} entity changes to the current audit log"); } @@ -343,6 +344,18 @@ protected virtual void PublishEventsForTrackedEntity(EntityEntry entry) EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); } } + else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && + (entry.Navigations.Any(x => x.IsModified) || AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entry))) + { + if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) + { + EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity); + } + else + { + EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); + } + } break; case EntityState.Deleted: @@ -353,7 +366,7 @@ protected virtual void PublishEventsForTrackedEntity(EntityEntry entry) if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) { - foreach (var entityEntry in ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged && AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(x))) + foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries()) { if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) { 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 a173f4562d3..d145ed53169 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 @@ -15,25 +15,21 @@ namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; /// public class AbpEfCoreNavigationHelper : ITransientDependency { - private Dictionary> EntityEntryNavigationProperties { get; } = new (); + protected Dictionary> EntityEntryNavigationProperties { get; } = new Dictionary>(); public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e) { - foreach (var entry in changeTracker.Entries()) - { - EntityEntryTrackedOrStateChanged(entry); - } + EntityEntryTrackedOrStateChanged(e.Entry); + DetectChanges(); } public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e) { - foreach (var entry in changeTracker.Entries()) - { - EntityEntryTrackedOrStateChanged(entry); - } + EntityEntryTrackedOrStateChanged(e.Entry); + DetectChanges(); } - private void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) + protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) { if (entityEntry.State != EntityState.Unchanged) { @@ -50,44 +46,79 @@ private void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) var index = 0; foreach (var navigationEntry in entityEntry.Navigations.Where(navigation => !navigation.IsModified)) { - if (!navigationEntry.IsLoaded && navigationEntry.CurrentValue == null) + var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); + if (navigationProperty != null) { - index++; - continue; + if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value)) + { + navigationProperty.Value = navigationEntry.CurrentValue; + } } - - var currentValue = navigationEntry.CurrentValue; - if (navigationEntry.CurrentValue is ICollection collection) + else { - currentValue = collection.Cast().ToList(); + navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, navigationEntry.CurrentValue, false, entityEntry, navigationEntry)); } - var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); - if (navigationProperty != null) + index++; + } + } + + protected virtual void DetectChanges() + { + foreach (var entityEntryNavigationProperties in EntityEntryNavigationProperties) + { + foreach (var navigationProperty in entityEntryNavigationProperties.Value.Where(x => !x.IsChanged && x.EntityEntry.State == EntityState.Unchanged)) { - if (!navigationProperty.IsChanged && (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value))) + if (navigationProperty.NavigationEntry.IsModified) { - navigationProperty.Value = currentValue; - navigationProperty.IsChanged = currentValue != null && !IsCollectionAndEmpty(currentValue); + navigationProperty.IsChanged = true; + continue; } - if (!navigationProperty.IsChanged && navigationProperty.Value != null && !IsCollectionAndEmpty(navigationProperty.Value)) + if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value)) { - navigationProperty.Value = currentValue; - navigationProperty.IsChanged = currentValue == null || IsCollectionAndEmpty(currentValue); + if (navigationProperty.NavigationEntry.CurrentValue != null || IsCollectionAndNotEmpty(navigationProperty.NavigationEntry.CurrentValue)) + { + if (navigationProperty.NavigationEntry.CurrentValue is ICollection collection) + { + navigationProperty.Value = collection.Cast().ToList(); + } + else + { + navigationProperty.Value = navigationProperty.NavigationEntry.CurrentValue; + } + } } - } - else - { - navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, currentValue, false)); - } - index++; + if (navigationProperty.Value != null || IsCollectionAndNotEmpty(navigationProperty.Value)) + { + if (navigationProperty.NavigationEntry.CurrentValue == null || IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue)) + { + if (IsCollectionAndEmpty(navigationProperty.Value) && IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue)) + { + continue; + } + + navigationProperty.IsChanged = true; + } + } + } } } - public bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) + public virtual List GetChangedEntityEntries() + { + DetectChanges(); + return EntityEntryNavigationProperties + .SelectMany(x => x.Value) + .Where(x => x.NavigationEntry.IsModified || x.IsChanged) + .Select(x => x.EntityEntry) + .ToList(); + } + + public virtual bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) { + DetectChanges(); if (entityEntry.State == EntityState.Modified) { return true; @@ -99,32 +130,19 @@ public bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) return false; } - var index = 0; - foreach (var navigationEntry in entityEntry.Navigations) + if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) { - if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As().TargetEntry?.State == EntityState.Modified)) - { - return true; - } - - EntityEntryTrackedOrStateChanged(entityEntry); - - if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) - { - var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); - if (navigationProperty != null && navigationProperty.IsChanged) - { - return true; - } - } - - index++; + return navigationProperties.Any(x => x.IsChanged) || + navigationProperties.Any(x => x.NavigationEntry.IsModified) || + navigationProperties.Any(x => + x.NavigationEntry is ReferenceEntry && + x.NavigationEntry.As().TargetEntry?.State == EntityState.Modified); } return false; } - public bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index) + public virtual bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index) { if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As().TargetEntry?.State == EntityState.Modified)) { @@ -149,6 +167,11 @@ public bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int return false; } + public void Clear() + { + EntityEntryNavigationProperties.Clear(); + } + private string? GetEntityId(EntityEntry entityEntry) { return entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1 @@ -160,4 +183,9 @@ private bool IsCollectionAndEmpty(object? value) { return value is ICollection && value is ICollection collection && collection.Count == 0; } + + private bool IsCollectionAndNotEmpty(object? value) + { + return value is ICollection && value is ICollection collection && collection.Count != 0; + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs index c9a4ae2ad83..a3cec7cf64c 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs @@ -1,3 +1,5 @@ +using Microsoft.EntityFrameworkCore.ChangeTracking; + namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; public class AbpEntityEntryNavigationProperty @@ -10,11 +12,17 @@ public class AbpEntityEntryNavigationProperty public bool IsChanged { get; set; } - public AbpEntityEntryNavigationProperty(int index, string name, object? value, bool isChanged) + public EntityEntry EntityEntry { get; set; } + + public NavigationEntry NavigationEntry { get; set; } + + public AbpEntityEntryNavigationProperty(int index, string name, object? value, bool isChanged, EntityEntry entityEntry, NavigationEntry navigationEntry) { Index = index; Name = name; Value = value; IsChanged = isChanged; + EntityEntry = entityEntry; + NavigationEntry = navigationEntry; } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 54d917e76b3..ef6bd230f58 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -255,7 +255,10 @@ protected virtual bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool def protected virtual bool HasNavigationPropertiesChanged(EntityEntry entityEntry) { - return entityEntry.State == EntityState.Unchanged && Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null && AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entityEntry); + return entityEntry.State == EntityState.Unchanged && + Options.SaveEntityHistoryWhenNavigationChanges && + AbpEfCoreNavigationHelper != null && + AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entityEntry); } protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue)