diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 236655e1a2..3d933435d8 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations.Schema; @@ -21,6 +20,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities.Events; using Volo.Abp.Domain.Repositories; +using Volo.Abp.EntityFrameworkCore.ChangeTrackers; using Volo.Abp.EntityFrameworkCore.EntityHistory; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.ValueConverters; @@ -246,6 +246,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, ChangeTracker.Tracked += ChangeTracker_Tracked; ChangeTracker.StateChanged += ChangeTracker_StateChanged; + EntityHistoryHelper.InitializeNavigationHelper(AbpEfCoreNavigationHelper); + if (UnitOfWorkManager is AlwaysDisableTransactionsUnitOfWorkManager) { Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; @@ -254,12 +256,14 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e) { + AbpEfCoreNavigationHelper.ChangeTracker_Tracked(ChangeTracker, sender, e); FillExtraPropertiesForTrackedEntities(e); PublishEventsForTrackedEntity(e.Entry); } protected virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e) { + AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(ChangeTracker, sender, e); PublishEventsForTrackedEntity(e.Entry); } @@ -311,7 +315,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected virtual void PublishEventsForTrackedEntity(EntityEntry entry) { - switch (entry.State) + switch (entry.State) { case EntityState.Added: ApplyAbpConceptsForAddedEntity(entry); @@ -320,7 +324,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, case EntityState.Modified: ApplyAbpConceptsForModifiedEntity(entry); - if (entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) + if (entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd))) { if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) { @@ -339,79 +343,20 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, break; } - foreach (var entityEntry in ChangeTracker.Entries().Where(HasEntityEntryChanged)) - { - ApplyAbpConceptsForModifiedEntity(entry); - - if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) - { - EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); - } - else - { - EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); - } - } - } - - protected virtual bool HasEntityEntryChanged(EntityEntry entry) - { - if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && entry.State == EntityState.Unchanged) + if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) { - var entryId = entry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1 ? entryEntity.GetKeys().FirstOrDefault()?.ToString() : null; - if (entryId != null) + foreach (var entityEntry in ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged && AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(x))) { - var entity = NavigationPropertyValues.GetOrAdd(entryId, () => new Dictionary()); - var index = 0; - foreach (var navigationEntry in entry.Navigations.Where(navigation => !navigation.IsModified && navigation.IsLoaded)) + if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As().IsDeleted) { - var navigationEntryValue = entity.GetOrAdd(index, () => navigationEntry.CurrentValue); - - if (navigationEntryValue != null && - navigationEntryValue is not ICollection && - navigationEntryValue != navigationEntry.CurrentValue) - { - return true; - } - - if (navigationEntryValue is ICollection navigationEntryValueCollection && - navigationEntry.CurrentValue is ICollection navigationEntryCurrentValueCollection && - navigationEntryValueCollection.Count != 0 && - navigationEntryCurrentValueCollection.Count == 0) - { - return true; - } - - entity[index] = navigationEntry.CurrentValue is ICollection - ? navigationEntry.CurrentValue.As().Cast().ToList() - : navigationEntry.CurrentValue; - - index++; + EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); + } + else + { + EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity); } } } - - if (entry.State != EntityState.Modified && entry.State != EntityState.Unchanged) - { - return false; - } - - var changed = entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)); - if (changed) - { - return true; - } - - if (!changed && - EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) - { - if (entry.Navigations.Any(navigation => navigation.IsModified || (navigation is ReferenceEntry && navigation.As().TargetEntry?.State == EntityState.Modified))) - { - changed = true; - } - } - - return changed; } protected virtual void HandlePropertiesBeforeSave() diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs new file mode 100644 index 0000000000..74f0831be2 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; + +/// +/// Refactor this class after EF Core supports this case. +/// https://github.com/dotnet/efcore/issues/24076#issuecomment-1996623874 +/// +public class AbpEfCoreNavigationHelper : ITransientDependency +{ + private Dictionary> EntityEntryNavigationProperties { get; } = new (); + + public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e) + { + foreach (var entry in changeTracker.Entries()) + { + EntityEntryTrackedOrStateChanged(entry); + } + } + + public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e) + { + foreach (var entry in changeTracker.Entries()) + { + EntityEntryTrackedOrStateChanged(entry); + } + } + + private void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) + { + if (entityEntry.State != EntityState.Unchanged) + { + return; + } + + var entryId = GetEntityId(entityEntry); + if (entryId == null) + { + return; + } + + var navigationProperties = EntityEntryNavigationProperties.GetOrAdd(entryId, () => new List()); + var index = 0; + foreach (var navigationEntry in entityEntry.Navigations.Where(navigation => !navigation.IsModified)) + { + if (!navigationEntry.IsLoaded) + { + index++; + continue; + } + + var currentValue = navigationEntry.CurrentValue; + if (navigationEntry.CurrentValue is ICollection collection) + { + currentValue = collection.Cast().ToList(); + } + + var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); + if (navigationProperty != null) + { + if (!navigationProperty.IsChanged && (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value))) + { + navigationProperty.Value = currentValue; + navigationProperty.IsChanged = currentValue != null && !IsCollectionAndEmpty(currentValue); + } + + if (!navigationProperty.IsChanged && navigationProperty.Value != null && !IsCollectionAndEmpty(navigationProperty.Value)) + { + navigationProperty.Value = currentValue; + navigationProperty.IsChanged = currentValue == null || IsCollectionAndEmpty(currentValue); + } + } + else + { + navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, currentValue, false)); + } + + index++; + } + } + + public bool IsEntityEntryNavigationChanged(EntityEntry entityEntry) + { + if (entityEntry.State == EntityState.Modified) + { + return true; + } + + var entryId = GetEntityId(entityEntry); + if (entryId == null) + { + return false; + } + + var index = 0; + foreach (var navigationEntry in entityEntry.Navigations) + { + if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As().TargetEntry?.State == EntityState.Modified)) + { + return true; + } + + EntityEntryTrackedOrStateChanged(entityEntry); + + if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) + { + var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); + if (navigationProperty != null && navigationProperty.IsChanged) + { + return true; + } + } + + index++; + } + + return false; + } + + public bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index) + { + if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As().TargetEntry?.State == EntityState.Modified)) + { + return true; + } + + var entryId = GetEntityId(navigationEntry.EntityEntry); + if (entryId == null) + { + return false; + } + + if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties)) + { + var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index); + if (navigationProperty != null && navigationProperty.IsChanged) + { + return true; + } + } + + return false; + } + + private string? GetEntityId(EntityEntry entityEntry) + { + return entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1 + ? entryEntity.GetKeys().FirstOrDefault()?.ToString() + : null; + } + + private bool IsCollectionAndEmpty(object? value) + { + return value is ICollection && value is ICollection collection && collection.Count == 0; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs new file mode 100644 index 0000000000..c9a4ae2ad8 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs @@ -0,0 +1,20 @@ +namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers; + +public class AbpEntityEntryNavigationProperty +{ + public int Index { get; set; } + + public string Name { get; set; } + + public object? Value { get; set; } + + public bool IsChanged { get; set; } + + public AbpEntityEntryNavigationProperty(int index, string name, object? value, bool isChanged) + { + Index = index; + Name = name; + Value = value; + IsChanged = isChanged; + } +} 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 5229e9aab6..54d917e76b 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 @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.Logging; @@ -13,6 +12,7 @@ using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Values; +using Volo.Abp.EntityFrameworkCore.ChangeTrackers; using Volo.Abp.Json; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; @@ -30,6 +30,8 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency protected IAuditingHelper AuditingHelper { get; } protected IClock Clock { get; } + protected AbpEfCoreNavigationHelper? AbpEfCoreNavigationHelper { get; set; } + public EntityHistoryHelper( IAuditingStore auditingStore, IOptions options, @@ -46,6 +48,11 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency Logger = NullLogger.Instance; } + public void InitializeNavigationHelper(AbpEfCoreNavigationHelper abpEfCoreNavigationHelper) + { + AbpEfCoreNavigationHelper = abpEfCoreNavigationHelper; + } + public virtual List CreateChangeList(ICollection entityEntries) { var list = new List(); @@ -186,11 +193,12 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } - if (Options.SaveEntityHistoryWhenNavigationChanges && entityEntry.State == EntityState.Unchanged) + if (entityEntry.State == EntityState.Unchanged && Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null) { + var index = 0; foreach (var navigation in entityEntry.Navigations) { - if (navigation.IsModified || (navigation is ReferenceEntry && navigation.As().TargetEntry?.State == EntityState.Modified)) + if (navigation.IsModified || AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(navigation, index)) { propertyChanges.Add(new EntityPropertyChangeInfo { @@ -198,6 +206,8 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency PropertyTypeFullName = navigation.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName! }); } + + index++; } } @@ -245,9 +255,7 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency protected virtual bool HasNavigationPropertiesChanged(EntityEntry entityEntry) { - return Options.SaveEntityHistoryWhenNavigationChanges && entityEntry.State == EntityState.Unchanged && - (entityEntry.Navigations.Any(navigationEntry => navigationEntry.IsModified) || - entityEntry.Navigations.Where(x => x is ReferenceEntry).Cast().Any(x => x.TargetEntry != null && x.TargetEntry.State == EntityState.Modified)); + return entityEntry.State == EntityState.Unchanged && Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null && AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entityEntry); } protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/IEntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/IEntityHistoryHelper.cs index 2a860ee9cf..ee8d9aa528 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/IEntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/IEntityHistoryHelper.cs @@ -1,11 +1,14 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; using Volo.Abp.Auditing; +using Volo.Abp.EntityFrameworkCore.ChangeTrackers; namespace Volo.Abp.EntityFrameworkCore.EntityHistory; public interface IEntityHistoryHelper { + void InitializeNavigationHelper(AbpEfCoreNavigationHelper abpEfCoreNavigationHelper); + List CreateChangeList(ICollection entityEntries); void UpdateChangeList(List entityChanges); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/NullEntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/NullEntityHistoryHelper.cs index 285b52e942..6e53848d8f 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/NullEntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/NullEntityHistoryHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking; using Volo.Abp.Auditing; +using Volo.Abp.EntityFrameworkCore.ChangeTrackers; namespace Volo.Abp.EntityFrameworkCore.EntityHistory; @@ -13,6 +14,11 @@ public class NullEntityHistoryHelper : IEntityHistoryHelper } + public void InitializeNavigationHelper(AbpEfCoreNavigationHelper abpEfCoreNavigationHelper) + { + + } + public List CreateChangeList(ICollection entityEntries) { return new List(); diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs index 3a21cfaf5a..65c9d0fa57 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs @@ -24,6 +24,10 @@ public class AbpAuditingTestModule : AbpModule context.Services.AddAbpDbContext(options => { options.AddDefaultRepositories(true); + options.Entity(opt => + { + opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany); + }); }); var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs index e68d9d94b8..e13523dabe 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs @@ -23,6 +23,8 @@ public class AppEntityWithNavigations : AggregateRoot public string FullName { get; set; } + public AppEntityWithValueObjectAddress AppEntityWithValueObjectAddress { get; set; } + public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; } public virtual List OneToMany { get; set; } @@ -45,7 +47,16 @@ public class AppEntityWithNavigationChildOneToMany : Entity } [Audited] -public class AppEntityWithNavigationChildManyToMany : Entity +public class AppEntityWithNavigationChildManyToMany : AggregateRoot { public string ChildName { get; set; } + + public virtual List ManyToMany { get; set; } +} + +public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany +{ + public Guid AppEntityWithNavigationsId { get; set; } + + public Guid AppEntityWithNavigationChildManyToManyId { get; set; } } 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 f6d4bf49ef..b102396cb0 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 @@ -48,9 +48,10 @@ public class AbpAuditingTestDbContext : AbpDbContext modelBuilder.Entity(b => { b.ConfigureByConvention(); + b.OwnsOne(x => x.AppEntityWithValueObjectAddress); b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); - b.HasMany(x => x.ManyToMany).WithMany(); + b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); }); } 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 a5060dc595..954e6222ee 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 @@ -385,10 +385,10 @@ public class Auditing_Tests : AbpAuditingTestBase #pragma warning disable 4014 AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 3 && - x.EntityChanges[0].ChangeType == EntityChangeType.Created && - x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && - x.EntityChanges[1].ChangeType == EntityChangeType.Updated && - x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Created && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && x.EntityChanges[2].ChangeType == EntityChangeType.Deleted && x.EntityChanges[2].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName)); AuditingStore.ClearReceivedCalls(); @@ -411,16 +411,16 @@ public class Auditing_Tests : AbpAuditingTestBase #pragma warning disable 4014 AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && x.EntityChanges[0].ChangeType == EntityChangeType.Updated && - x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && x.EntityChanges[0].PropertyChanges.Count == 1 && - x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && - x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" && - x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"" && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObject.AppEntityWithValueObjectAddress) && x.EntityChanges[1].ChangeType == EntityChangeType.Updated && - x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && - x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObject.AppEntityWithValueObjectAddress))); + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == "\"England\"" && + x.EntityChanges[1].PropertyChanges[0].NewValue == "\"Germany\"")); AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 @@ -439,11 +439,16 @@ public class Auditing_Tests : AbpAuditingTestBase } #pragma warning disable 4014 - AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && - x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && - x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && - x.EntityChanges[0].PropertyChanges.Count == 2 && - x.EntityChanges[0].PropertyChanges.All(p => p.NewValue == null))); + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObject.AppEntityWithValueObjectAddress) && + + x.EntityChanges[1].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && + x.EntityChanges[1].PropertyChanges.Count == 2 && + x.EntityChanges[1].PropertyChanges.All(p => p.NewValue == null))); #pragma warning restore 4014 } @@ -511,6 +516,33 @@ public class Auditing_Tests : AbpAuditingTestBase AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToOne = null; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).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.OneToOne) && + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) @@ -544,6 +576,32 @@ public class Auditing_Tests : AbpAuditingTestBase AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToMany = null; + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + 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).FullName)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) @@ -574,6 +632,36 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.ManyToMany.Clear(); + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + + x.EntityChanges[1].ChangeType == EntityChangeType.Updated && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName && + x.EntityChanges[1].PropertyChanges.Count == 1 && + x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildManyToMany.ManyToMany) && + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); + #pragma warning restore 4014 } } @@ -648,10 +736,10 @@ public class Auditing_SaveEntityHistoryWhenNavigationChanges_Tests : AbpAuditing #pragma warning disable 4014 AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 3 && - x.EntityChanges[0].ChangeType == EntityChangeType.Created && - x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && - x.EntityChanges[1].ChangeType == EntityChangeType.Updated && - x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[0].ChangeType == EntityChangeType.Updated && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Created && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName && x.EntityChanges[2].ChangeType == EntityChangeType.Deleted && x.EntityChanges[2].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName)); AuditingStore.ClearReceivedCalls(); @@ -767,6 +855,31 @@ public class Auditing_SaveEntityHistoryWhenNavigationChanges_Tests : AbpAuditing AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToOne = null; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName && + x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildOneToOne.ChildName) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) @@ -800,6 +913,32 @@ public class Auditing_SaveEntityHistoryWhenNavigationChanges_Tests : AbpAuditing AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + entity.OneToMany.Clear(); + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 && + x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && + x.EntityChanges[0].PropertyChanges.Count == 2 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildOneToMany.AppEntityWithNavigationId) && + x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(Guid).FullName && + x.EntityChanges[0].PropertyChanges[1].PropertyName == nameof(AppEntityWithNavigationChildOneToMany.ChildName) && + x.EntityChanges[0].PropertyChanges[1].PropertyTypeFullName == typeof(string).FullName)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 702a9f34c7..5de4bd6369 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -73,7 +73,7 @@ public class TestMigrationsDbContext : AbpDbContext b.OwnsOne(v => v.AppEntityWithValueObjectAddress); b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); - b.HasMany(x => x.ManyToMany).WithMany(); + b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 568c711f89..63b57ec147 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -100,7 +100,7 @@ public class TestAppDbContext : AbpDbContext, IThirdDbContext, b.OwnsOne(v => v.AppEntityWithValueObjectAddress); b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id); b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId); - b.HasMany(x => x.ManyToMany).WithMany(); + b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity(); }); modelBuilder.TryConfigureObjectExtensions(); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs index bde51a3ddb..b0c2e4f7bf 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs @@ -61,7 +61,16 @@ public class AppEntityWithNavigationChildOneToMany : Entity public string ChildName { get; set; } } -public class AppEntityWithNavigationChildManyToMany : Entity +public class AppEntityWithNavigationChildManyToMany : AggregateRoot { public string ChildName { get; set; } + + public virtual List ManyToMany { get; set; } +} + +public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany +{ + public Guid AppEntityWithNavigationsId { get; set; } + + public Guid AppEntityWithNavigationChildManyToManyId { get; set; } }