Browse Source

Get the before and after changes of navigation properties.

pull/21873/head
maliming 1 year ago
parent
commit
17c4e6be53
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 24
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  2. 64
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
  3. 54
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  4. 72
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

24
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs

@ -31,6 +31,14 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
{
if (entityEntry.State is EntityState.Unchanged or EntityState.Modified)
{
foreach (var entry in EntityEntries.Values.Where(x => x.NavigationEntries.Any()))
{
entry.UpdateNavigationEntries();
}
}
if (entityEntry.State != EntityState.Unchanged)
{
return;
@ -189,6 +197,22 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return navigationEntryProperty != null && navigationEntryProperty.IsModified;
}
public virtual AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return null;
}
if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
{
return null;
}
return abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
}
protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry)
{
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)

64
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -32,6 +33,49 @@ public class AbpEntityEntry
EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
public void UpdateNavigationEntries()
{
foreach (var navigationEntry in NavigationEntries)
{
if (IsModified ||
EntityEntry.State == EntityState.Modified ||
navigationEntry.IsModified ||
navigationEntry.NavigationEntry.IsModified)
{
continue;
}
var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name);
var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue);
if (currentValue == null)
{
continue;
}
switch (navigationEntry.OriginalValue)
{
case null:
navigationEntry.OriginalValue = currentValue;
break;
case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection:
{
var existingList = originalValueCollection.Cast<object?>().ToList();
var newList = currentValueCollection.Cast<object?>().ToList();
if (newList.Count > existingList.Count)
{
navigationEntry.OriginalValue = currentValue;
}
break;
}
default:
navigationEntry.OriginalValue = currentValue;
break;
}
}
}
}
public class AbpNavigationEntry
@ -42,9 +86,29 @@ public class AbpNavigationEntry
public bool IsModified { get; set; }
public List<object>? OriginalValue { get; set; }
public object? CurrentValue => NavigationEntry.CurrentValue;
public AbpNavigationEntry(NavigationEntry navigationEntry, string name)
{
NavigationEntry = navigationEntry;
Name = name;
OriginalValue = GetOriginalValue(navigationEntry.CurrentValue);
}
public static List<object>? GetOriginalValue(object? currentValue)
{
if (currentValue is null)
{
return null;
}
if (currentValue is IEnumerable enumerable)
{
return enumerable.Cast<object>().ToList();
}
return new List<object> { currentValue };
}
}

54
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -199,10 +200,14 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
{
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index))
{
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index);
var isCollection = navigationEntry.Metadata.IsCollection;
propertyChanges.Add(new EntityPropertyChangeInfo
{
PropertyName = navigationEntry.Metadata.Name,
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
});
}
}
@ -211,6 +216,53 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
return propertyChanges;
}
protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection)
{
switch (entity)
{
case null:
return null;
case IEntity entryEntity:
var keys = entryEntity.GetKeys();
if (keys.Length == 0)
{
return null;
}
var serializedKeys = keys.Length == 1 && !isCollection
? keys[0]?.ToString()
: JsonSerializer.Serialize(keys);
return serializedKeys?.TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength);
case IEnumerable enumerable:
var keysList = new List<string>();
foreach (var item in enumerable)
{
var id = GetNavigationPropertyValue(item, false);
if (id != null)
{
keysList.Add(id);
}
}
if (keysList.Count == 0)
{
return null;
}
var serializedKeysEnumerable = keysList.Count == 1 && !isCollection
? keysList.First()
: JsonSerializer.Serialize(keysList);
return serializedKeysEnumerable.TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength);
default:
return null;
}
}
protected virtual bool IsCreated(EntityEntry entityEntry)
{
return entityEntry.State == EntityState.Added;

72
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@ -512,7 +513,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName));
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == entityId.ToString()));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
@ -539,10 +542,13 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName));
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == entityId.ToString() &&
x.EntityChanges[1].PropertyChanges[0].NewValue == null));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
var oneToManyId = "";
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
@ -561,6 +567,8 @@ public class Auditing_Tests : AbpAuditingTestBase
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
oneToManyId = entity.OneToMany.First().Id.ToString();
}
}
@ -572,36 +580,80 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName));
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{oneToManyId}\"]"));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
var newOneToManyId = "";
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.OneToMany = null;
entity.OneToMany.Add(new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName2"
});
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList());
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 2 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName));
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == $"[\"{oneToManyId}\"]" &&
x.EntityChanges[1].PropertyChanges[0].NewValue == newOneToManyId));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList());
entity.OneToMany = null;
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 3 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[1].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
x.EntityChanges[2].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[2].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[2].PropertyChanges.Count == 1 &&
x.EntityChanges[2].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
x.EntityChanges[2].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildOneToMany>).FullName &&
x.EntityChanges[2].PropertyChanges[0].OriginalValue == newOneToManyId &&
x.EntityChanges[2].PropertyChanges[0].NewValue == null));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
var manyToManyId = "";
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
@ -619,6 +671,8 @@ public class Auditing_Tests : AbpAuditingTestBase
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
manyToManyId = entity.ManyToMany.First().Id.ToString();
}
}
@ -630,7 +684,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName));
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName &&
x.EntityChanges[1].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{manyToManyId}\"]"));
#pragma warning restore 4014
@ -655,6 +711,8 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[0].PropertyChanges.Count == 1 &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigationChildManyToMany>).FullName &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == $"[\"{manyToManyId}\"]" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == null &&
x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName &&

Loading…
Cancel
Save