diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index 674b338fcd..9a1e8e3cdf 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/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 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); + } + } + /// /// Determines the CLR type of a property based on its EF Core metadata and the values in the given . /// diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs index 609294a4dc..be399318cd 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs +++ b/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 public AppEntityContactInformation ContactInformation { get; set; } + [DisableAuditing] + public AppEntityContactInformation DisabledContactInformation { get; set; } + public AppEntityWithComplexProperty() { } @@ -23,4 +27,11 @@ public class AppEntityWithComplexProperty : FullAuditedAggregateRoot 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; } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs index 7927c33f74..a4d0564e90 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs @@ -85,6 +85,19 @@ public class AbpAuditingTestDbContext : AbpDbContext 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(); + }); }); }); } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 35229efbdf..b614b43fb7 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/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(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