Browse Source

Merge pull request #24767 from abpframework/ComplexProperty

Support auditing and history for complex properties
pull/24785/head
Engincan VESKE 1 week ago
committed by GitHub
parent
commit
0bac6c66df
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 37
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 62
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  3. 1
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  4. 37
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithComplexProperty.cs
  5. 23
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  6. 162
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  7. 156
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs
  8. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  9. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  10. 32
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs
  11. 4
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs

37
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -310,7 +310,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity);
}
}
else if (entityEntry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
else if (GetAllPropertyEntries(entityEntry).Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{
if (IsOnlyForeignKeysModified(entityEntry))
{
@ -446,7 +446,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break;
case EntityState.Modified:
if (entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
if (GetAllPropertyEntries(entry).Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{
if (IsOnlyForeignKeysModified(entry))
{
@ -454,7 +454,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break;
}
var modifiedProperties = entry.Properties.Where(x => x.IsModified).ToList();
var modifiedProperties = GetAllPropertyEntries(entry).Where(x => x.IsModified).ToList();
var disableAuditingAttributes = modifiedProperties.Select(x => x.Metadata.PropertyInfo?.GetCustomAttribute<DisableAuditingAttribute>()).ToList();
if (disableAuditingAttributes.Any(x => x == null || x.UpdateModificationProps))
{
@ -501,9 +501,36 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
}
}
protected virtual IEnumerable<PropertyEntry> GetAllPropertyEntries(EntityEntry entry)
{
return entry.Properties.Concat(GetAllComplexPropertyEntries(entry.ComplexProperties));
}
protected virtual IEnumerable<PropertyEntry> GetAllComplexPropertyEntries(IEnumerable<ComplexPropertyEntry> complexPropertyEntries)
{
foreach (var complexPropertyEntry in complexPropertyEntries)
{
var complexPropertyInfo = complexPropertyEntry.Metadata.PropertyInfo;
if (complexPropertyInfo != null && complexPropertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
{
continue;
}
foreach (var propertyEntry in complexPropertyEntry.Properties)
{
yield return propertyEntry;
}
foreach (var nestedPropertyEntry in GetAllComplexPropertyEntries(complexPropertyEntry.ComplexProperties))
{
yield return nestedPropertyEntry;
}
}
}
protected virtual bool IsOnlyForeignKeysModified(EntityEntry entry)
{
return entry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey() &&
return GetAllPropertyEntries(entry).Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey() &&
(x.CurrentValue == null || x.OriginalValue?.ToString() == x.CurrentValue?.ToString()));
}
@ -662,7 +689,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, bool forceApply = false)
{
if (forceApply ||
entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
GetAllPropertyEntries(entry).Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{
IncrementEntityVersionProperty(entry);
SetModificationAuditProperties(entry);

62
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);
@ -207,6 +208,17 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
}
}
foreach (var complexPropertyEntry in entityEntry.ComplexProperties)
{
AddComplexPropertyChanges(
complexPropertyEntry,
propertyChanges,
isCreated,
isDeleted,
isSoftDeleted,
parentPath: null);
}
if (AbpEfCoreNavigationHelper == null)
{
return propertyChanges;
@ -250,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>
@ -262,7 +320,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
/// <see cref="PropertyEntry.OriginalValue"/>. If both values are <c>null</c>, the declared CLR type
/// (which may remain <see cref="object"/>) is returned.
/// </returns>
protected virtual Type DeterminePropertyTypeFromEntry(IProperty property, PropertyEntry propertyEntry)
protected virtual Type DeterminePropertyTypeFromEntry(IReadOnlyPropertyBase property, PropertyEntry propertyEntry)
{
var propertyType = property.ClrType.GetFirstGenericArgumentIfNullable();

1
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs

@ -61,6 +61,7 @@ public class AbpAuditingTestModule : AbpModule
);
options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty)));
options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithComplexProperty), type => type == typeof(AppEntityWithComplexProperty)));
});
context.Services.AddType<Auditing_Tests.MyAuditedObject1>();

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

@ -0,0 +1,37 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.Auditing.App.Entities;
public class AppEntityWithComplexProperty : FullAuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public AppEntityContactInformation ContactInformation { get; set; }
[DisableAuditing]
public AppEntityContactInformation DisabledContactInformation { get; set; }
public AppEntityWithComplexProperty()
{
}
public AppEntityWithComplexProperty(Guid id, string name)
: base(id)
{
Name = name;
}
}
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;
}

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

@ -31,6 +31,7 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
public DbSet<AppEntityWithNavigationsAndDisableAuditing> AppEntityWithNavigationsAndDisableAuditing { get; set; }
public DbSet<AppEntityWithJsonProperty> EntitiesWithObjectProperty { get; set; }
public DbSet<AppEntityWithComplexProperty> AppEntitiesWithComplexProperty { get; set; }
public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options)
: base(options)
@ -77,5 +78,27 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
);
});
});
modelBuilder.Entity<AppEntityWithComplexProperty>(b =>
{
b.ConfigureByConvention();
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();
});
});
});
}
}

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

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute;
using Shouldly;
using Volo.Abp.Auditing.App.Entities;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
@ -820,6 +821,167 @@ public class Auditing_Tests : AbpAuditingTestBase
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
}
[Fact]
public async Task Should_Write_AuditLog_For_Complex_Property_Changes()
{
var entityId = Guid.NewGuid();
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithComplexProperty, Guid>>();
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = new AppEntityWithComplexProperty(entityId, "Test Entity")
{
ContactInformation = new AppEntityContactInformation
{
Street = "First Street",
Location = new AppEntityContactLocation
{
City = "First City"
}
},
DisabledContactInformation = new AppEntityContactInformation
{
Street = "Disabled Street",
Location = new AppEntityContactLocation
{
City = "Disabled City"
}
}
};
await repository.InsertAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
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 == 3 &&
x.EntityChanges[0].PropertyChanges.Any(pc =>
pc.PropertyName == nameof(AppEntityWithComplexProperty.Name) &&
pc.OriginalValue == null &&
pc.NewValue == "\"Test Entity\"" &&
pc.PropertyTypeFullName == typeof(string).FullName) &&
x.EntityChanges[0].PropertyChanges.Any(pc =>
pc.PropertyName == "ContactInformation.Street" &&
pc.OriginalValue == null &&
pc.NewValue == "\"First Street\"" &&
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
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.ContactInformation.Location.City = "Updated City";
entity.DisabledContactInformation.Street = "Updated Disabled Street";
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 &&
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.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
}
[Fact]
public async Task Should_Not_Update_Modification_Audit_Properties_When_Only_Disabled_Complex_Property_Changes()
{
var entityId = Guid.NewGuid();
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithComplexProperty, Guid>>();
using (var uow = _unitOfWorkManager.Begin())
{
var entity = new AppEntityWithComplexProperty(entityId, "Test Entity")
{
ContactInformation = new AppEntityContactInformation
{
Street = "First Street",
Location = new AppEntityContactLocation
{
City = "First City"
}
},
DisabledContactInformation = new AppEntityContactInformation
{
Street = "Disabled Street",
Location = new AppEntityContactLocation
{
City = "Disabled City"
}
}
};
await repository.InsertAsync(entity);
await uow.CompleteAsync();
}
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.Name = "Updated Test Entity";
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
}
DateTime? lastModificationTime;
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
lastModificationTime = entity.LastModificationTime;
lastModificationTime.ShouldNotBeNull();
await uow.CompleteAsync();
}
await Task.Delay(10);
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.DisabledContactInformation.Street = "Updated Disabled Street";
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
}
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.LastModificationTime.ShouldBe(lastModificationTime);
await uow.CompleteAsync();
}
}
}
public class Auditing_DisableLogActionInfo_Tests : Auditing_Tests

156
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Auditing/Auditing_Tests.cs

@ -6,6 +6,7 @@ using NSubstitute;
using Shouldly;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
using Xunit;
@ -85,6 +86,33 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
}));
}
[Fact]
public async Task Should_Set_Modification_If_Complex_Properties_Changed()
{
var city = Guid.NewGuid().ToString();
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.Location.City = city;
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.ContactInformation.ShouldNotBeNull();
douglas.ContactInformation!.Location.City.ShouldBe(city);
douglas.LastModificationTime.ShouldNotBeNull();
douglas.LastModificationTime.Value.ShouldBeLessThanOrEqualTo(Clock.Now);
douglas.LastModifierId.ShouldBe(CurrentUserId);
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_Set_Modification_If_Properties_HasDisableAuditing_UpdateModificationProps()
{
@ -106,6 +134,50 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_Set_Modification_If_ComplexProperties_HasDisableAuditing_UpdateModificationProps()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.DisableAuditingUpdateModificationPropsProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldBeNull();
douglas.LastModifierId.ShouldBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_Set_Modification_If_Nested_ComplexProperties_HasDisableAuditing_UpdateModificationProps()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.Location.DisableAuditingUpdateModificationPropsProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldBeNull();
douglas.LastModifierId.ShouldBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_PublishEntityEvent_If_Properties_HasDisableAuditing_PublishEntityEventProperty()
{
@ -126,6 +198,48 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
EntityChangeEventHelper.DidNotReceive().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_PublishEntityEvent_If_ComplexProperties_HasDisableAuditing_PublishEntityEventProperty()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.DisableAuditingPublishEntityEventProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.DidNotReceive().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Not_PublishEntityEvent_If_Nested_ComplexProperties_HasDisableAuditing_PublishEntityEventProperty()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.Location.DisableAuditingPublishEntityEventProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.DidNotReceive().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Set_Modification_And_PublishEntityEvent_If_Properties_HasDisableAuditing()
@ -146,4 +260,46 @@ public class Auditing_Tests : Auditing_Tests<AbpEntityFrameworkCoreTestModule>
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Set_Modification_And_PublishEntityEvent_If_ComplexProperties_HasDisableAuditing()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.DisableAuditingProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
[Fact]
public async Task Should_Set_Modification_And_PublishEntityEvent_If_Nested_ComplexProperties_HasDisableAuditing()
{
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId);
douglas.ContactInformation ??= new PersonContactInformation();
douglas.ContactInformation.Location.DisableAuditingProperty = Guid.NewGuid().ToString();
}));
await WithUnitOfWorkAsync((async () =>
{
var douglas = await PersonRepository.FindAsync(TestDataBuilder.UserDouglasId);
douglas.ShouldNotBeNull();
douglas.LastModificationTime.ShouldNotBeNull();
}));
EntityChangeEventHelper.Received().PublishEntityUpdatedEvent(Arg.Any<object>());
}
}

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -78,6 +78,14 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.Property(x => x.HasDefaultValue).HasDefaultValue(DateTime.Now);
b.Property(x => x.TenantId).HasColumnName("Tenant_Id");
b.Property(x => x.IsDeleted).HasColumnName("Is_Deleted");
b.ComplexProperty(x => x.ContactInformation, cb =>
{
cb.Property(x => x.Street).IsRequired();
cb.ComplexProperty(x => x.Location, locationBuilder =>
{
locationBuilder.Property(x => x.City).IsRequired();
});
});
});
modelBuilder.Entity<City>(b =>

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -92,6 +92,14 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.Property(x => x.HasDefaultValue).HasDefaultValue(DateTime.Now);
b.Property(x => x.TenantId).HasColumnName("Tenant_Id");
b.Property(x => x.IsDeleted).HasColumnName("Is_Deleted");
b.ComplexProperty(x => x.ContactInformation, cb =>
{
cb.Property(x => x.Street).IsRequired();
cb.ComplexProperty(x => x.Location, locationBuilder =>
{
locationBuilder.Property(x => x.City).IsRequired();
});
});
});
modelBuilder

32
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/Person.cs

@ -32,6 +32,8 @@ public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant, IHasEntityVe
public virtual DateTime HasDefaultValue { get; set; }
public virtual PersonContactInformation? ContactInformation { get; set; }
public int EntityVersion { get; set; }
[DisableAuditing(UpdateModificationProps = false)]
@ -84,3 +86,33 @@ public class Person : FullAuditedAggregateRoot<Guid>, IMultiTenant, IHasEntityVe
);
}
}
public class PersonContactInformation
{
public string Street { get; set; } = string.Empty;
public PersonContactLocation Location { get; set; } = new();
[DisableAuditing(UpdateModificationProps = false)]
public string? DisableAuditingUpdateModificationPropsProperty { get; set; }
[DisableAuditing(PublishEntityEvent = false)]
public string? DisableAuditingPublishEntityEventProperty { get; set; }
[DisableAuditing]
public string? DisableAuditingProperty { get; set; }
}
public class PersonContactLocation
{
public string City { get; set; } = string.Empty;
[DisableAuditing(UpdateModificationProps = false)]
public string? DisableAuditingUpdateModificationPropsProperty { get; set; }
[DisableAuditing(PublishEntityEvent = false)]
public string? DisableAuditingPublishEntityEventProperty { get; set; }
[DisableAuditing]
public string? DisableAuditingProperty { get; set; }
}

4
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs

@ -76,6 +76,10 @@ public class TestDataBuilder : ITransientDependency
private async Task AddPeople()
{
var douglas = new Person(UserDouglasId, "Douglas", 42, cityId: LondonCityId);
douglas.ContactInformation = new PersonContactInformation
{
Street = "Test Street"
};
douglas.Phones.Add(new Phone(douglas.Id, "123456789"));
douglas.Phones.Add(new Phone(douglas.Id, "123456780", PhoneType.Home));

Loading…
Cancel
Save