Browse Source

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

pull/24767/head
maliming 6 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 isCreated = IsCreated(entityEntry);
var isDeleted = IsDeleted(entityEntry);
var isSoftDeleted = IsSoftDeleted(entityEntry);
foreach (var property in properties)
{
@ -193,7 +194,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
}
var propertyEntry = entityEntry.Property(property.Name);
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry))
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !isSoftDeleted)
{
var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry);
@ -209,19 +210,13 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
foreach (var complexPropertyEntry in entityEntry.ComplexProperties)
{
foreach (var propertyEntry in complexPropertyEntry.Properties)
{
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry))
{
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 = $"{complexPropertyEntry.Metadata.Name}.{propertyEntry.Metadata.Name}",
PropertyTypeFullName = propertyEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
});
}
}
AddComplexPropertyChanges(
complexPropertyEntry,
propertyChanges,
isCreated,
isDeleted,
isSoftDeleted,
parentPath: null);
}
if (AbpEfCoreNavigationHelper == null)
@ -267,6 +262,52 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
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>
/// Determines the CLR type of a property based on its EF Core metadata and the values in the given <see cref="PropertyEntry"/>.
/// </summary>

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

@ -1,4 +1,5 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.Auditing.App.Entities;
@ -9,6 +10,9 @@ public class AppEntityWithComplexProperty : FullAuditedAggregateRoot<Guid>
public AppEntityContactInformation ContactInformation { get; set; }
[DisableAuditing]
public AppEntityContactInformation DisabledContactInformation { get; set; }
public AppEntityWithComplexProperty()
{
}
@ -23,4 +27,11 @@ public class AppEntityWithComplexProperty : FullAuditedAggregateRoot<Guid>
public class AppEntityContactInformation
{
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 =>
{
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
{
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 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
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 =>
pc.PropertyName == nameof(AppEntityWithComplexProperty.Name) &&
pc.OriginalValue == null &&
@ -860,7 +872,14 @@ public class Auditing_Tests : AbpAuditingTestBase
pc.PropertyName == "ContactInformation.Street" &&
pc.OriginalValue == null &&
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();
#pragma warning restore 4014
@ -870,7 +889,8 @@ public class Auditing_Tests : AbpAuditingTestBase
{
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);
@ -884,9 +904,9 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithComplexProperty).FullName &&
x.EntityChanges[0].PropertyChanges.Count == 1 &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == "ContactInformation.Street" &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"First Street\"" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated Street\"" &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == "ContactInformation.Location.City" &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"First City\"" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated City\"" &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014

Loading…
Cancel
Save