Browse Source

Enhance auditing for complex properties by adding support for nested complex property changes and updating related tests

pull/24767/head
maliming 8 months ago
parent
commit
45557f9ae5
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 69
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  2. 11
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs
  3. 13
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  4. 34
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

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

@ -184,6 +184,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
var properties = entityEntry.Metadata.GetProperties(); var properties = entityEntry.Metadata.GetProperties();
var isCreated = IsCreated(entityEntry); var isCreated = IsCreated(entityEntry);
var isDeleted = IsDeleted(entityEntry); var isDeleted = IsDeleted(entityEntry);
var isSoftDeleted = IsSoftDeleted(entityEntry);
foreach (var property in properties) foreach (var property in properties)
{ {
@ -193,7 +194,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
} }
var propertyEntry = entityEntry.Property(property.Name); var propertyEntry = entityEntry.Property(property.Name);
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !isSoftDeleted)
{ {
var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry); var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry);
@ -209,19 +210,13 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
foreach (var complexPropertyEntry in entityEntry.ComplexProperties) foreach (var complexPropertyEntry in entityEntry.ComplexProperties)
{ {
foreach (var propertyEntry in complexPropertyEntry.Properties) AddComplexPropertyChanges(
{ complexPropertyEntry,
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) propertyChanges,
{ isCreated,
propertyChanges.Add(new EntityPropertyChangeInfo isDeleted,
{ isSoftDeleted,
NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), parentPath: null);
OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
PropertyName = $"{complexPropertyEntry.Metadata.Name}.{propertyEntry.Metadata.Name}",
PropertyTypeFullName = propertyEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
});
}
}
} }
if (AbpEfCoreNavigationHelper == null) if (AbpEfCoreNavigationHelper == null)
@ -267,6 +262,52 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
return propertyChanges; return propertyChanges;
} }
protected virtual void AddComplexPropertyChanges(
ComplexPropertyEntry complexPropertyEntry,
List<EntityPropertyChangeInfo> propertyChanges,
bool isCreated,
bool isDeleted,
bool isSoftDeleted,
string? parentPath)
{
var complexPropertyInfo = complexPropertyEntry.Metadata.PropertyInfo;
if (complexPropertyInfo != null && complexPropertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
{
return;
}
var complexPropertyPath = parentPath == null
? complexPropertyEntry.Metadata.Name
: $"{parentPath}.{complexPropertyEntry.Metadata.Name}";
foreach (var propertyEntry in complexPropertyEntry.Properties)
{
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !isSoftDeleted)
{
var propertyType = DeterminePropertyTypeFromEntry(propertyEntry.Metadata, propertyEntry);
propertyChanges.Add(new EntityPropertyChangeInfo
{
NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
PropertyName = $"{complexPropertyPath}.{propertyEntry.Metadata.Name}",
PropertyTypeFullName = propertyType.FullName!
});
}
}
foreach (var nestedComplexPropertyEntry in complexPropertyEntry.ComplexProperties)
{
AddComplexPropertyChanges(
nestedComplexPropertyEntry,
propertyChanges,
isCreated,
isDeleted,
isSoftDeleted,
complexPropertyPath);
}
}
/// <summary> /// <summary>
/// Determines the CLR type of a property based on its EF Core metadata and the values in the given <see cref="PropertyEntry"/>. /// Determines the CLR type of a property based on its EF Core metadata and the values in the given <see cref="PropertyEntry"/>.
/// </summary> /// </summary>

11
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs

@ -1,4 +1,5 @@
using System; using System;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.Auditing.App.Entities; namespace Volo.Abp.Auditing.App.Entities;
@ -9,6 +10,9 @@ public class AppEntityWithComplexProperty : FullAuditedAggregateRoot<Guid>
public AppEntityContactInformation ContactInformation { get; set; } public AppEntityContactInformation ContactInformation { get; set; }
[DisableAuditing]
public AppEntityContactInformation DisabledContactInformation { get; set; }
public AppEntityWithComplexProperty() public AppEntityWithComplexProperty()
{ {
} }
@ -23,4 +27,11 @@ public class AppEntityWithComplexProperty : FullAuditedAggregateRoot<Guid>
public class AppEntityContactInformation public class AppEntityContactInformation
{ {
public string Street { get; set; } = string.Empty; public string Street { get; set; } = string.Empty;
public AppEntityContactLocation Location { get; set; } = new();
}
public class AppEntityContactLocation
{
public string City { get; set; } = string.Empty;
} }

13
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs

@ -85,6 +85,19 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
b.ComplexProperty(x => x.ContactInformation, cb => b.ComplexProperty(x => x.ContactInformation, cb =>
{ {
cb.Property(x => x.Street).IsRequired(); cb.Property(x => x.Street).IsRequired();
cb.ComplexProperty(x => x.Location, lb =>
{
lb.Property(x => x.City).IsRequired();
});
});
b.ComplexProperty(x => x.DisabledContactInformation, cb =>
{
cb.Property(x => x.Street).IsRequired();
cb.ComplexProperty(x => x.Location, lb =>
{
lb.Property(x => x.City).IsRequired();
});
}); });
}); });
} }

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

@ -835,7 +835,19 @@ public class Auditing_Tests : AbpAuditingTestBase
{ {
ContactInformation = new AppEntityContactInformation ContactInformation = new AppEntityContactInformation
{ {
Street = "First Street" Street = "First Street",
Location = new AppEntityContactLocation
{
City = "First City"
}
},
DisabledContactInformation = new AppEntityContactInformation
{
Street = "Disabled Street",
Location = new AppEntityContactLocation
{
City = "Disabled City"
}
} }
}; };
@ -850,7 +862,7 @@ public class Auditing_Tests : AbpAuditingTestBase
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 && AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Created && x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithComplexProperty).FullName && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithComplexProperty).FullName &&
x.EntityChanges[0].PropertyChanges.Count == 2 && x.EntityChanges[0].PropertyChanges.Count == 3 &&
x.EntityChanges[0].PropertyChanges.Any(pc => x.EntityChanges[0].PropertyChanges.Any(pc =>
pc.PropertyName == nameof(AppEntityWithComplexProperty.Name) && pc.PropertyName == nameof(AppEntityWithComplexProperty.Name) &&
pc.OriginalValue == null && pc.OriginalValue == null &&
@ -860,7 +872,14 @@ public class Auditing_Tests : AbpAuditingTestBase
pc.PropertyName == "ContactInformation.Street" && pc.PropertyName == "ContactInformation.Street" &&
pc.OriginalValue == null && pc.OriginalValue == null &&
pc.NewValue == "\"First Street\"" && pc.NewValue == "\"First Street\"" &&
pc.PropertyTypeFullName == typeof(string).FullName))); pc.PropertyTypeFullName == typeof(string).FullName) &&
x.EntityChanges[0].PropertyChanges.Any(pc =>
pc.PropertyName == "ContactInformation.Location.City" &&
pc.OriginalValue == null &&
pc.NewValue == "\"First City\"" &&
pc.PropertyTypeFullName == typeof(string).FullName) &&
x.EntityChanges[0].PropertyChanges.All(pc =>
!pc.PropertyName.StartsWith(nameof(AppEntityWithComplexProperty.DisabledContactInformation)))));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
@ -870,7 +889,8 @@ public class Auditing_Tests : AbpAuditingTestBase
{ {
var entity = await repository.GetAsync(entityId); var entity = await repository.GetAsync(entityId);
entity.ContactInformation.Street = "Updated Street"; entity.ContactInformation.Location.City = "Updated City";
entity.DisabledContactInformation.Street = "Updated Disabled Street";
await repository.UpdateAsync(entity); await repository.UpdateAsync(entity);
@ -884,9 +904,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[0].ChangeType == EntityChangeType.Updated && x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithComplexProperty).FullName && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithComplexProperty).FullName &&
x.EntityChanges[0].PropertyChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == "ContactInformation.Street" && x.EntityChanges[0].PropertyChanges[0].PropertyName == "ContactInformation.Location.City" &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"First Street\"" && x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"First City\"" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated Street\"" && x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated City\"" &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName)); x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName));
AuditingStore.ClearReceivedCalls(); AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014

Loading…
Cancel
Save