|
|
|
@ -4,6 +4,9 @@ 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; |
|
|
|
|
|
|
|
@ -15,18 +18,18 @@ namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; |
|
|
|
/// </summary>
|
|
|
|
public class AbpEfCoreNavigationHelper : ITransientDependency |
|
|
|
{ |
|
|
|
protected Dictionary<string, List<AbpEntityEntryNavigationProperty>> EntityEntryNavigationProperties { get; } = new Dictionary<string, List<AbpEntityEntryNavigationProperty>>(); |
|
|
|
protected Dictionary<string, AbpEntityEntry> EntityEntries { get; } = new(); |
|
|
|
|
|
|
|
public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e) |
|
|
|
{ |
|
|
|
EntityEntryTrackedOrStateChanged(e.Entry); |
|
|
|
DetectChanges(); |
|
|
|
DetectChanges(changeTracker, e.Entry); |
|
|
|
} |
|
|
|
|
|
|
|
public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e) |
|
|
|
{ |
|
|
|
EntityEntryTrackedOrStateChanged(e.Entry); |
|
|
|
DetectChanges(); |
|
|
|
DetectChanges(changeTracker, e.Entry); |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) |
|
|
|
@ -42,83 +45,103 @@ public class AbpEfCoreNavigationHelper : ITransientDependency |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var navigationProperties = EntityEntryNavigationProperties.GetOrAdd(entryId, () => new List<AbpEntityEntryNavigationProperty>()); |
|
|
|
var index = 0; |
|
|
|
foreach (var navigationEntry in entityEntry.Navigations.Where(navigation => !navigation.IsModified)) |
|
|
|
if (EntityEntries.ContainsKey(entryId)) |
|
|
|
{ |
|
|
|
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); |
|
|
|
if (navigationProperty != null) |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
EntityEntries.Add(entryId, new AbpEntityEntry(entryId, entityEntry)); |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual void DetectChanges(ChangeTracker changeTracker, EntityEntry entityEntry) |
|
|
|
{ |
|
|
|
if (entityEntry.State != EntityState.Added && |
|
|
|
entityEntry.State != EntityState.Deleted && |
|
|
|
entityEntry.State != EntityState.Modified) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
#pragma warning disable EF1001
|
|
|
|
var stateManager = changeTracker.Context.GetDependencies().StateManager; |
|
|
|
var internalEntityEntityEntry = stateManager.Entries.FirstOrDefault(x => x.Entity == entityEntry.Entity); |
|
|
|
if (internalEntityEntityEntry == null) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var foreignKeys = entityEntry.Metadata.GetForeignKeys().ToList(); |
|
|
|
foreach (var foreignKey in foreignKeys) |
|
|
|
{ |
|
|
|
var principal = stateManager.FindPrincipal(internalEntityEntityEntry, foreignKey); |
|
|
|
if (principal == null) |
|
|
|
{ |
|
|
|
if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value)) |
|
|
|
{ |
|
|
|
navigationProperty.Value = navigationEntry.CurrentValue; |
|
|
|
} |
|
|
|
continue; |
|
|
|
} |
|
|
|
else |
|
|
|
|
|
|
|
var entryId = GetEntityId(principal.ToEntityEntry()); |
|
|
|
if (entryId == null || !EntityEntries.TryGetValue(entryId, out var abpEntityEntry)) |
|
|
|
{ |
|
|
|
navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, navigationEntry.CurrentValue, false, entityEntry, navigationEntry)); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
index++; |
|
|
|
abpEntityEntry.IsModified = true; |
|
|
|
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) |
|
|
|
{ |
|
|
|
navigationEntry.IsModified = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual void DetectChanges() |
|
|
|
{ |
|
|
|
foreach (var entityEntryNavigationProperties in EntityEntryNavigationProperties) |
|
|
|
var skipNavigations = entityEntry.Metadata.GetSkipNavigations().ToList(); |
|
|
|
foreach (var skipNavigation in skipNavigations) |
|
|
|
{ |
|
|
|
foreach (var navigationProperty in entityEntryNavigationProperties.Value.Where(x => !x.IsChanged && x.EntityEntry.State == EntityState.Unchanged)) |
|
|
|
var joinEntityType = skipNavigation.JoinEntityType; |
|
|
|
var foreignKey = skipNavigation.ForeignKey; |
|
|
|
var inverseForeignKey = skipNavigation.Inverse.ForeignKey; |
|
|
|
foreach (var joinEntry in stateManager.Entries) |
|
|
|
{ |
|
|
|
if (navigationProperty.NavigationEntry.IsModified) |
|
|
|
if (joinEntry.EntityType != joinEntityType || stateManager.FindPrincipal(joinEntry, foreignKey) != internalEntityEntityEntry) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
var principal = stateManager.FindPrincipal(joinEntry, inverseForeignKey); |
|
|
|
if (principal == null) |
|
|
|
{ |
|
|
|
navigationProperty.IsChanged = true; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value)) |
|
|
|
var entryId = GetEntityId(principal.ToEntityEntry()); |
|
|
|
if (entryId == null || !EntityEntries.TryGetValue(entryId, out var abpEntityEntry)) |
|
|
|
{ |
|
|
|
if (navigationProperty.NavigationEntry.CurrentValue != null || IsCollectionAndNotEmpty(navigationProperty.NavigationEntry.CurrentValue)) |
|
|
|
{ |
|
|
|
if (navigationProperty.NavigationEntry.CurrentValue is ICollection collection) |
|
|
|
{ |
|
|
|
navigationProperty.Value = collection.Cast<object?>().ToList(); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
navigationProperty.Value = navigationProperty.NavigationEntry.CurrentValue; |
|
|
|
} |
|
|
|
} |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (navigationProperty.Value != null || IsCollectionAndNotEmpty(navigationProperty.Value)) |
|
|
|
abpEntityEntry.IsModified = true; |
|
|
|
abpEntityEntry.IsModified = true; |
|
|
|
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) |
|
|
|
{ |
|
|
|
if (navigationProperty.NavigationEntry.CurrentValue == null || IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue)) |
|
|
|
{ |
|
|
|
if (IsCollectionAndEmpty(navigationProperty.Value) && IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue)) |
|
|
|
{ |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
navigationProperty.IsChanged = true; |
|
|
|
} |
|
|
|
navigationEntry.IsModified = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
#pragma warning restore EF1001
|
|
|
|
} |
|
|
|
|
|
|
|
public virtual List<EntityEntry> GetChangedEntityEntries() |
|
|
|
{ |
|
|
|
DetectChanges(); |
|
|
|
return EntityEntryNavigationProperties |
|
|
|
.SelectMany(x => x.Value) |
|
|
|
.Where(x => x.NavigationEntry.IsModified || x.IsChanged) |
|
|
|
.Select(x => x.EntityEntry) |
|
|
|
return EntityEntries |
|
|
|
.Where(x => x.Value.IsModified) |
|
|
|
.Select(x => x.Value.EntityEntry) |
|
|
|
.ToList(); |
|
|
|
} |
|
|
|
|
|
|
|
public virtual bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) |
|
|
|
public virtual bool IsEntityEntryModified(EntityEntry entityEntry) |
|
|
|
{ |
|
|
|
DetectChanges(); |
|
|
|
if (entityEntry.State == EntityState.Modified) |
|
|
|
{ |
|
|
|
return true; |
|
|
|
@ -130,62 +153,35 @@ public class AbpEfCoreNavigationHelper : ITransientDependency |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) |
|
|
|
{ |
|
|
|
return navigationProperties.Any(x => x.IsChanged) || |
|
|
|
navigationProperties.Any(x => x.NavigationEntry.IsModified) || |
|
|
|
navigationProperties.Any(x => |
|
|
|
x.NavigationEntry is ReferenceEntry && |
|
|
|
x.NavigationEntry.As<ReferenceEntry>().TargetEntry?.State == EntityState.Modified); |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
return EntityEntries.TryGetValue(entryId, out var abpEntityEntry) && abpEntityEntry.IsModified; |
|
|
|
} |
|
|
|
|
|
|
|
public virtual bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index) |
|
|
|
public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, NavigationEntry navigationEntry, int index) |
|
|
|
{ |
|
|
|
if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As<ReferenceEntry>().TargetEntry?.State == EntityState.Modified)) |
|
|
|
{ |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
var entryId = GetEntityId(navigationEntry.EntityEntry); |
|
|
|
var entryId = GetEntityId(entityEntry); |
|
|
|
if (entryId == null) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) |
|
|
|
if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry)) |
|
|
|
{ |
|
|
|
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); |
|
|
|
if (navigationProperty != null && navigationProperty.IsChanged) |
|
|
|
{ |
|
|
|
return true; |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
var navigationEntryProperty = abpEntityEntry.NavigationEntries.ElementAtOrDefault(index); |
|
|
|
return navigationEntryProperty != null && navigationEntryProperty.IsModified; |
|
|
|
} |
|
|
|
|
|
|
|
public void Clear() |
|
|
|
{ |
|
|
|
EntityEntryNavigationProperties.Clear(); |
|
|
|
} |
|
|
|
|
|
|
|
private string? GetEntityId(EntityEntry entityEntry) |
|
|
|
protected virtual string? GetEntityId(EntityEntry entityEntry) |
|
|
|
{ |
|
|
|
return entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1 |
|
|
|
? entryEntity.GetKeys().FirstOrDefault()?.ToString() |
|
|
|
: null; |
|
|
|
} |
|
|
|
|
|
|
|
private bool IsCollectionAndEmpty(object? value) |
|
|
|
{ |
|
|
|
return value is ICollection && value is ICollection collection && collection.Count == 0; |
|
|
|
} |
|
|
|
|
|
|
|
private bool IsCollectionAndNotEmpty(object? value) |
|
|
|
public void Clear() |
|
|
|
{ |
|
|
|
return value is ICollection && value is ICollection collection && collection.Count != 0; |
|
|
|
EntityEntries.Clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
|