Browse Source

Add `AbpEfCoreNavigationHelper`.

pull/19079/head
maliming 2 years ago
parent
commit
d503f292e3
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 85
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 163
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  3. 20
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs
  4. 20
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  5. 3
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/IEntityHistoryHelper.cs
  6. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/NullEntityHistoryHelper.cs
  7. 4
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  8. 13
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs
  9. 3
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  10. 177
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  11. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  12. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  13. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs

85
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<TDbContext> : 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<TDbContext> : 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<TDbContext> : 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<TDbContext> : 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<ISoftDelete>().IsDeleted)
{
@ -339,79 +343,20 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break;
}
foreach (var entityEntry in ChangeTracker.Entries().Where(HasEntityEntryChanged))
{
ApplyAbpConceptsForModifiedEntity(entry);
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().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<int, object?>());
var index = 0;
foreach (var navigationEntry in entry.Navigations.Where(navigation => !navigation.IsModified && navigation.IsLoaded))
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().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<ICollection>().Cast<object?>().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<ReferenceEntry>().TargetEntry?.State == EntityState.Modified)))
{
changed = true;
}
}
return changed;
}
protected virtual void HandlePropertiesBeforeSave()

163
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;
/// <summary>
/// Refactor this class after EF Core supports this case.
/// https://github.com/dotnet/efcore/issues/24076#issuecomment-1996623874
/// </summary>
public class AbpEfCoreNavigationHelper : ITransientDependency
{
private Dictionary<string, List<AbpEntityEntryNavigationProperty>> 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<AbpEntityEntryNavigationProperty>());
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<object?>().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<ReferenceEntry>().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<ReferenceEntry>().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;
}
}

20
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;
}
}

20
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<AbpAuditingOptions> options,
@ -46,6 +48,11 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
Logger = NullLogger<EntityHistoryHelper>.Instance;
}
public void InitializeNavigationHelper(AbpEfCoreNavigationHelper abpEfCoreNavigationHelper)
{
AbpEfCoreNavigationHelper = abpEfCoreNavigationHelper;
}
public virtual List<EntityChangeInfo> CreateChangeList(ICollection<EntityEntry> entityEntries)
{
var list = new List<EntityChangeInfo>();
@ -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<ReferenceEntry>().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<ReferenceEntry>().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)

3
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<EntityChangeInfo> CreateChangeList(ICollection<EntityEntry> entityEntries);
void UpdateChangeList(List<EntityChangeInfo> entityChanges);

6
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<EntityChangeInfo> CreateChangeList(ICollection<EntityEntry> entityEntries)
{
return new List<EntityChangeInfo>();

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

@ -24,6 +24,10 @@ public class AbpAuditingTestModule : AbpModule
context.Services.AddAbpDbContext<AbpAuditingTestDbContext>(options =>
{
options.AddDefaultRepositories(true);
options.Entity<AppEntityWithNavigations>(opt =>
{
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany);
});
});
var sqliteConnection = CreateDatabaseAndGetConnection();

13
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs

@ -23,6 +23,8 @@ public class AppEntityWithNavigations : AggregateRoot<Guid>
public string FullName { get; set; }
public AppEntityWithValueObjectAddress AppEntityWithValueObjectAddress { get; set; }
public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; }
public virtual List<AppEntityWithNavigationChildOneToMany> OneToMany { get; set; }
@ -45,7 +47,16 @@ public class AppEntityWithNavigationChildOneToMany : Entity<Guid>
}
[Audited]
public class AppEntityWithNavigationChildManyToMany : Entity<Guid>
public class AppEntityWithNavigationChildManyToMany : AggregateRoot<Guid>
{
public string ChildName { get; set; }
public virtual List<AppEntityWithNavigations> ManyToMany { get; set; }
}
public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany
{
public Guid AppEntityWithNavigationsId { get; set; }
public Guid AppEntityWithNavigationChildManyToManyId { get; set; }
}

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

@ -48,9 +48,10 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
modelBuilder.Entity<AppEntityWithNavigations>(b =>
{
b.ConfigureByConvention();
b.OwnsOne(x => x.AppEntityWithValueObjectAddress);
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(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<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
}

177
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<AuditLogInfo>(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<AuditLogInfo>(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<AuditLogInfo>(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<AuditLogInfo>(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<AuditLogInfo>(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<AuditLogInfo>(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<AppEntityWithNavigationChildOneToMany>).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<AppEntityWithNavigationChildManyToMany>).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<AuditLogInfo>(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<AppEntityWithNavigationChildManyToMany>).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<AppEntityWithNavigations>).FullName));
#pragma warning restore 4014
}
}
@ -648,10 +736,10 @@ public class Auditing_SaveEntityHistoryWhenNavigationChanges_Tests : AbpAuditing
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(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<AuditLogInfo>(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<AuditLogInfo>(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())

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

@ -73,7 +73,7 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
b.OwnsOne(v => v.AppEntityWithValueObjectAddress);
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(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<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
}
}

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

@ -100,7 +100,7 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.OwnsOne(v => v.AppEntityWithValueObjectAddress);
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOne>(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<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();

11
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs

@ -61,7 +61,16 @@ public class AppEntityWithNavigationChildOneToMany : Entity<Guid>
public string ChildName { get; set; }
}
public class AppEntityWithNavigationChildManyToMany : Entity<Guid>
public class AppEntityWithNavigationChildManyToMany : AggregateRoot<Guid>
{
public string ChildName { get; set; }
public virtual List<AppEntityWithNavigations> ManyToMany { get; set; }
}
public class AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany
{
public Guid AppEntityWithNavigationsId { get; set; }
public Guid AppEntityWithNavigationChildManyToManyId { get; set; }
}

Loading…
Cancel
Save