mirror of https://github.com/abpframework/abp.git
committed by
GitHub
26 changed files with 780 additions and 39 deletions
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.Domain.Entities.Events; |
||||
|
|
||||
|
public class AbpEntityChangeOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default: true.
|
||||
|
/// Publish the EntityUpdatedEvent when any navigation property changes.
|
||||
|
/// </summary>
|
||||
|
public bool PublishEntityUpdatedEventWhenNavigationChanges { get; set; } = true; |
||||
|
} |
||||
@ -0,0 +1,163 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.EntityFrameworkCore.ChangeTracking; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Refactor this class after EF Core supports this case.
|
||||
|
/// https://github.com/dotnet/efcore/issues/24076#issuecomment-1996623874
|
||||
|
/// </summary>
|
||||
|
public class AbpEfCoreNavigationHelper : ITransientDependency |
||||
|
{ |
||||
|
private Dictionary<string, List<AbpEntityEntryNavigationProperty>> EntityEntryNavigationProperties { get; } = new (); |
||||
|
|
||||
|
public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e) |
||||
|
{ |
||||
|
foreach (var entry in changeTracker.Entries()) |
||||
|
{ |
||||
|
EntityEntryTrackedOrStateChanged(entry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e) |
||||
|
{ |
||||
|
foreach (var entry in changeTracker.Entries()) |
||||
|
{ |
||||
|
EntityEntryTrackedOrStateChanged(entry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) |
||||
|
{ |
||||
|
if (entityEntry.State != EntityState.Unchanged) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var entryId = GetEntityId(entityEntry); |
||||
|
if (entryId == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var navigationProperties = EntityEntryNavigationProperties.GetOrAdd(entryId, () => new List<AbpEntityEntryNavigationProperty>()); |
||||
|
var index = 0; |
||||
|
foreach (var navigationEntry in entityEntry.Navigations.Where(navigation => !navigation.IsModified)) |
||||
|
{ |
||||
|
if (!navigationEntry.IsLoaded) |
||||
|
{ |
||||
|
index++; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var currentValue = navigationEntry.CurrentValue; |
||||
|
if (navigationEntry.CurrentValue is ICollection collection) |
||||
|
{ |
||||
|
currentValue = collection.Cast<object?>().ToList(); |
||||
|
} |
||||
|
|
||||
|
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); |
||||
|
if (navigationProperty != null) |
||||
|
{ |
||||
|
if (!navigationProperty.IsChanged && (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value))) |
||||
|
{ |
||||
|
navigationProperty.Value = currentValue; |
||||
|
navigationProperty.IsChanged = currentValue != null && !IsCollectionAndEmpty(currentValue); |
||||
|
} |
||||
|
|
||||
|
if (!navigationProperty.IsChanged && navigationProperty.Value != null && !IsCollectionAndEmpty(navigationProperty.Value)) |
||||
|
{ |
||||
|
navigationProperty.Value = currentValue; |
||||
|
navigationProperty.IsChanged = currentValue == null || IsCollectionAndEmpty(currentValue); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, currentValue, false)); |
||||
|
} |
||||
|
|
||||
|
index++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) |
||||
|
{ |
||||
|
if (entityEntry.State == EntityState.Modified) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
var entryId = GetEntityId(entityEntry); |
||||
|
if (entryId == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var index = 0; |
||||
|
foreach (var navigationEntry in entityEntry.Navigations) |
||||
|
{ |
||||
|
if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As<ReferenceEntry>().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 false; |
||||
|
} |
||||
|
|
||||
|
public bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index) |
||||
|
{ |
||||
|
if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As<ReferenceEntry>().TargetEntry?.State == EntityState.Modified)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
var entryId = GetEntityId(navigationEntry.EntityEntry); |
||||
|
if (entryId == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) |
||||
|
{ |
||||
|
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); |
||||
|
if (navigationProperty != null && navigationProperty.IsChanged) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private 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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; |
||||
|
|
||||
|
public class AbpEntityEntryNavigationProperty |
||||
|
{ |
||||
|
public int Index { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public object? Value { get; set; } |
||||
|
|
||||
|
public bool IsChanged { get; set; } |
||||
|
|
||||
|
public AbpEntityEntryNavigationProperty(int index, string name, object? value, bool isChanged) |
||||
|
{ |
||||
|
Index = index; |
||||
|
Name = name; |
||||
|
Value = value; |
||||
|
IsChanged = isChanged; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
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<Guid> |
||||
|
{ |
||||
|
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<AppEntityWithNavigationChildOneToMany> OneToMany { get; set; } |
||||
|
|
||||
|
public virtual List<AppEntityWithNavigationChildManyToMany> ManyToMany { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class AppEntityWithValueObjectAddress : ValueObject |
||||
|
{ |
||||
|
public AppEntityWithValueObjectAddress(string country) |
||||
|
{ |
||||
|
|
||||
|
Country = country; |
||||
|
} |
||||
|
|
||||
|
public string Country { get; set; } |
||||
|
|
||||
|
protected override IEnumerable<object> GetAtomicValues() |
||||
|
{ |
||||
|
yield return Country; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public class AppEntityWithNavigationChildOneToOne : Entity<Guid> |
||||
|
{ |
||||
|
public string ChildName { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class AppEntityWithNavigationChildOneToMany : Entity<Guid> |
||||
|
{ |
||||
|
public Guid AppEntityWithNavigationId { get; set; } |
||||
|
|
||||
|
public string ChildName { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class AppEntityWithNavigationChildManyToMany : AggregateRoot<Guid> |
||||
|
{ |
||||
|
public string ChildName { get; set; } |
||||
|
|
||||
|
public virtual List<AppEntityWithNavigations> ManyToMany { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany |
||||
|
{ |
||||
|
public Guid AppEntityWithNavigationsId { get; set; } |
||||
|
|
||||
|
public Guid AppEntityWithNavigationChildManyToManyId { get; set; } |
||||
|
} |
||||
Loading…
Reference in new issue