Browse Source

Merge pull request #19597 from abpframework/auto-merge/rel-8-0/2648

Merge branch rel-8.1 with rel-8.0
pull/19598/head
maliming 2 years ago
committed by GitHub
parent
commit
f60d92388a
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. 202
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  3. 50
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
  4. 28
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs
  5. 18
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  6. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  7. 268
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs
  8. 4
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs
  9. 14
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  10. 12
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  11. 18
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs
  12. 153
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

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

@ -162,6 +162,21 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{
try
{
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{
foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries())
{
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity);
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity);
}
}
}
var auditLog = AuditingManager?.Current?.Log;
List<EntityChangeInfo>? entityChangeList = null;
if (auditLog != null)
@ -259,14 +274,14 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
AbpEfCoreNavigationHelper.ChangeTracker_Tracked(ChangeTracker, sender, e);
AbpEfCoreNavigationHelper.ChangeTracker_Tracked(sender, e);
FillExtraPropertiesForTrackedEntities(e);
PublishEventsForTrackedEntity(e.Entry);
}
protected virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(ChangeTracker, sender, e);
AbpEfCoreNavigationHelper.ChangeTracker_StateChanged(sender, e);
PublishEventsForTrackedEntity(e.Entry);
}
@ -344,8 +359,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity);
}
}
else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges &&
(entry.Navigations.Any(x => x.IsModified) || AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entry)))
else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && AbpEfCoreNavigationHelper.IsEntityEntryModified(entry))
{
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{
@ -363,21 +377,6 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
break;
}
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{
foreach (var entityEntry in AbpEfCoreNavigationHelper.GetChangedEntityEntries())
{
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity);
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityEntry.Entity);
}
}
}
}
protected virtual void HandlePropertiesBeforeSave()

202
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs

@ -1,9 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -15,18 +15,18 @@ namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers;
/// </summary>
public class AbpEfCoreNavigationHelper : ITransientDependency
{
protected Dictionary<string, List<AbpEntityEntryNavigationProperty>> EntityEntryNavigationProperties { get; } = new Dictionary<string, List<AbpEntityEntryNavigationProperty>>();
protected Dictionary<string, AbpEntityEntry> EntityEntries { get; } = new();
public virtual void ChangeTracker_Tracked(ChangeTracker changeTracker, object? sender, EntityTrackedEventArgs e)
public virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges();
DetectChanges(e.Entry);
}
public virtual void ChangeTracker_StateChanged(ChangeTracker changeTracker, object? sender, EntityStateChangedEventArgs e)
public virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
EntityEntryTrackedOrStateChanged(e.Entry);
DetectChanges();
DetectChanges(e.Entry);
}
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
@ -36,156 +36,166 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return;
}
var entryId = GetEntityId(entityEntry);
var entryId = GetEntityEntryIdentity(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 (EntityEntries.ContainsKey(entryId))
{
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index);
if (navigationProperty != null)
return;
}
EntityEntries.Add(entryId, new AbpEntityEntry(entryId, entityEntry));
}
protected virtual void DetectChanges(EntityEntry entityEntry)
{
if (entityEntry.State != EntityState.Added &&
entityEntry.State != EntityState.Deleted &&
entityEntry.State != EntityState.Modified)
{
return;
}
RecursiveDetectChanges(entityEntry);
}
protected virtual void RecursiveDetectChanges(EntityEntry entityEntry)
{
#pragma warning disable EF1001
var stateManager = entityEntry.Context.GetDependencies().StateManager;
var internalEntityEntityEntry = stateManager.Entries.FirstOrDefault(x => x.Entity == entityEntry.Entity);
if (internalEntityEntityEntry == null)
{
return;
}
var foreignKeys = entityEntry.Metadata.GetForeignKeys().ToList();
foreach (var foreignKey in foreignKeys)
{
var principal = stateManager.FindPrincipal(internalEntityEntityEntry, foreignKey);
if (principal == null)
{
if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value))
{
navigationProperty.Value = navigationEntry.CurrentValue;
}
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
{
continue;
}
else
if (!abpEntityEntry.IsModified)
{
navigationProperties.Add(new AbpEntityEntryNavigationProperty(index, navigationEntry.Metadata.Name, navigationEntry.CurrentValue, false, entityEntry, navigationEntry));
abpEntityEntry.IsModified = true;
RecursiveDetectChanges(abpEntityEntry.EntityEntry);
}
index++;
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == foreignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == foreignKey);
if (navigationEntry != null)
{
navigationEntry.IsModified = true;
}
}
}
protected virtual void DetectChanges()
{
foreach (var entityEntryNavigationProperties in EntityEntryNavigationProperties)
var skipNavigations = entityEntry.Metadata.GetSkipNavigations().ToList();
foreach (var skipNavigation in skipNavigations)
{
foreach (var navigationProperty in entityEntryNavigationProperties.Value.Where(x => !x.IsChanged && x.EntityEntry.State == EntityState.Unchanged))
var joinEntityType = skipNavigation.JoinEntityType;
var foreignKey = skipNavigation.ForeignKey;
var inverseForeignKey = skipNavigation.Inverse.ForeignKey;
foreach (var joinEntry in stateManager.Entries)
{
if (navigationProperty.NavigationEntry.IsModified)
if (joinEntry.EntityType != joinEntityType || stateManager.FindPrincipal(joinEntry, foreignKey) != internalEntityEntityEntry)
{
navigationProperty.IsChanged = true;
continue;
}
if (navigationProperty.Value == null || IsCollectionAndEmpty(navigationProperty.Value))
var principal = stateManager.FindPrincipal(joinEntry, inverseForeignKey);
if (principal == null)
{
if (navigationProperty.NavigationEntry.CurrentValue != null || IsCollectionAndNotEmpty(navigationProperty.NavigationEntry.CurrentValue))
{
if (navigationProperty.NavigationEntry.CurrentValue is ICollection collection)
{
navigationProperty.Value = collection.Cast<object?>().ToList();
}
else
{
navigationProperty.Value = navigationProperty.NavigationEntry.CurrentValue;
}
}
continue;
}
var entryId = GetEntityEntryIdentity(principal.ToEntityEntry());
if (entryId == null || !EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
{
continue;
}
if (!abpEntityEntry.IsModified)
{
abpEntityEntry.IsModified = true;
RecursiveDetectChanges(abpEntityEntry.EntityEntry);
}
if (navigationProperty.Value != null || IsCollectionAndNotEmpty(navigationProperty.Value))
var navigationEntry = abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is INavigation navigationMetadata && navigationMetadata.ForeignKey == inverseForeignKey) ??
abpEntityEntry.NavigationEntries.FirstOrDefault(x => x.NavigationEntry.Metadata is ISkipNavigation skipNavigationMetadata && skipNavigationMetadata.ForeignKey == inverseForeignKey);
if (navigationEntry != null)
{
if (navigationProperty.NavigationEntry.CurrentValue == null || IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue))
{
if (IsCollectionAndEmpty(navigationProperty.Value) && IsCollectionAndEmpty(navigationProperty.NavigationEntry.CurrentValue))
{
continue;
}
navigationProperty.IsChanged = true;
}
navigationEntry.IsModified = true;
}
}
}
#pragma warning restore EF1001
}
public virtual List<EntityEntry> GetChangedEntityEntries()
{
DetectChanges();
return EntityEntryNavigationProperties
.SelectMany(x => x.Value)
.Where(x => x.NavigationEntry.IsModified || x.IsChanged)
.Select(x => x.EntityEntry)
return EntityEntries
.Where(x => x.Value.IsModified)
.Select(x => x.Value.EntityEntry)
.ToList();
}
public virtual bool IsEntityEntryNavigationChanged(EntityEntry entityEntry)
public virtual bool IsEntityEntryModified(EntityEntry entityEntry)
{
DetectChanges();
if (entityEntry.State == EntityState.Modified)
{
return true;
}
var entryId = GetEntityId(entityEntry);
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties))
{
return navigationProperties.Any(x => x.IsChanged) ||
navigationProperties.Any(x => x.NavigationEntry.IsModified) ||
navigationProperties.Any(x =>
x.NavigationEntry is ReferenceEntry &&
x.NavigationEntry.As<ReferenceEntry>().TargetEntry?.State == EntityState.Modified);
}
return false;
return EntityEntries.TryGetValue(entryId, out var abpEntityEntry) && abpEntityEntry.IsModified;
}
public virtual bool IsEntityEntryNavigationChanged(NavigationEntry navigationEntry, int index)
public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, int navigationEntryIndex)
{
if (navigationEntry.IsModified || (navigationEntry is ReferenceEntry && navigationEntry.As<ReferenceEntry>().TargetEntry?.State == EntityState.Modified))
{
return true;
}
var entryId = GetEntityId(navigationEntry.EntityEntry);
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
{
return false;
}
if (EntityEntryNavigationProperties.TryGetValue(entryId, out var navigationProperties))
if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
{
var navigationProperty = navigationProperties.FirstOrDefault(x => x.Index == index);
if (navigationProperty != null && navigationProperty.IsChanged)
{
return true;
}
return false;
}
return false;
var navigationEntryProperty = abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
return navigationEntryProperty != null && navigationEntryProperty.IsModified;
}
public void Clear()
protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry)
{
EntityEntryNavigationProperties.Clear();
}
private string? GetEntityId(EntityEntry entityEntry)
{
return entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1
? entryEntity.GetKeys().FirstOrDefault()?.ToString()
: null;
}
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)
{
return $"{entityEntry.Metadata.ClrType.FullName}:{entryEntity.GetKeys().FirstOrDefault()}";
}
private bool IsCollectionAndEmpty(object? value)
{
return value is ICollection && value is ICollection collection && collection.Count == 0;
return null;
}
private bool IsCollectionAndNotEmpty(object? value)
public void Clear()
{
return value is ICollection && value is ICollection collection && collection.Count != 0;
EntityEntries.Clear();
}
}

50
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace Volo.Abp.EntityFrameworkCore.ChangeTrackers;
public class AbpEntityEntry
{
public string Id { get; set; }
public EntityEntry EntityEntry { get; set; }
public List<AbpNavigationEntry> NavigationEntries { get; set; }
private bool _isModified;
public bool IsModified
{
get
{
return _isModified || EntityEntry.State == EntityState.Modified || NavigationEntries.Any(n => n.IsModified);
}
set
{
_isModified = value;
}
}
public AbpEntityEntry(string id, EntityEntry entityEntry)
{
Id = id;
EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
}
public class AbpNavigationEntry
{
public NavigationEntry NavigationEntry { get; set; }
public string Name { get; set; }
public bool IsModified { get; set; }
public AbpNavigationEntry(NavigationEntry navigationEntry, string name)
{
NavigationEntry = navigationEntry;
Name = name;
}
}

28
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntryNavigationProperty.cs

@ -1,28 +0,0 @@
using Microsoft.EntityFrameworkCore.ChangeTracking;
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 EntityEntry EntityEntry { get; set; }
public NavigationEntry NavigationEntry { get; set; }
public AbpEntityEntryNavigationProperty(int index, string name, object? value, bool isChanged, EntityEntry entityEntry, NavigationEntry navigationEntry)
{
Index = index;
Name = name;
Value = value;
IsChanged = isChanged;
EntityEntry = entityEntry;
NavigationEntry = navigationEntry;
}
}

18
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -193,21 +193,18 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
}
}
if (entityEntry.State == EntityState.Unchanged && Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null)
if (Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null)
{
var index = 0;
foreach (var navigation in entityEntry.Navigations)
foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i )))
{
if (navigation.IsModified || AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(navigation, index))
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index))
{
propertyChanges.Add(new EntityPropertyChangeInfo
{
PropertyName = navigation.Metadata.Name,
PropertyTypeFullName = navigation.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
PropertyName = navigationEntry.Metadata.Name,
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
});
}
index++;
}
}
@ -255,10 +252,9 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
protected virtual bool HasNavigationPropertiesChanged(EntityEntry entityEntry)
{
return entityEntry.State == EntityState.Unchanged &&
Options.SaveEntityHistoryWhenNavigationChanges &&
return Options.SaveEntityHistoryWhenNavigationChanges &&
AbpEfCoreNavigationHelper != null &&
AbpEfCoreNavigationHelper.IsEntityEntryNavigationChanged(entityEntry);
AbpEfCoreNavigationHelper.IsEntityEntryModified(entityEntry);
}
protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue)

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

@ -51,7 +51,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
options.Entity<AppEntityWithNavigations>(opt =>
{
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).Include(p => p.OneToMany).Include(p => p.ManyToMany);
opt.DefaultWithDetailsFunc = q => q.Include(p => p.OneToOne).ThenInclude(x => x.OneToOne).Include(p => p.OneToMany).ThenInclude(x => x.OneToMany).Include(p => p.ManyToMany);
});
});

268
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/DomainEvents/DomainEvents_Tests.cs

@ -26,11 +26,14 @@ public class AbpEfCoreDomainEvents_Tests : EntityFrameworkCoreTestBase
{
protected readonly IRepository<AppEntityWithNavigations, Guid> AppEntityWithNavigationsRepository;
protected readonly ILocalEventBus LocalEventBus;
protected readonly IRepository<Person, Guid> PersonRepository;
protected bool _loadEntityWithDetails = false;
public AbpEfCoreDomainEvents_Tests()
{
AppEntityWithNavigationsRepository = GetRequiredService<IRepository<AppEntityWithNavigations, Guid>>();
LocalEventBus = GetRequiredService<ILocalEventBus>();
PersonRepository = GetRequiredService<IRepository<Person, Guid>>();
}
protected override void AfterAddApplication(IServiceCollection services)
@ -39,16 +42,279 @@ public class AbpEfCoreDomainEvents_Tests : EntityFrameworkCoreTestBase
{
options.Entity<AppEntityWithNavigations>(opt =>
{
opt.DefaultWithDetailsFunc = q => q;
if (_loadEntityWithDetails)
{
opt.DefaultWithDetailsFunc = q => q;
}
});
});
base.AfterAddApplication(services);
}
[Fact]
public async Task Should_Trigger_Domain_Events_For_Aggregate_Root_When_Navigation_Changes_Tests()
{
_loadEntityWithDetails = false;
var entityId = Guid.NewGuid();
await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity"));
var entityUpdatedEventTriggered = false;
var personCreatedEventCount = 0;
var entityUpdatedEventTriggerCount = 0;
LocalEventBus.Subscribe<EntityCreatedEventData<Person>>(_ =>
{
personCreatedEventCount++;
return Task.CompletedTask;
});
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigations>>(async _ =>
{
entityUpdatedEventTriggered = true;
await PersonRepository.InsertAsync(new Person(Guid.NewGuid(), Guid.NewGuid().ToString(), new Random().Next(1, 100)));
});
// Test with simple property
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with value object
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey");
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress.Country = "USA";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithValueObjectAddress>>(_ =>
{
throw new Exception("Should not trigger this event");
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress = null;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with one to one
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = new AppEntityWithNavigationChildOneToOne
{
ChildName = "ChildName",
OneToOne = new AppEntityWithNavigationChildOneToOneAndOneToOne
{
ChildName = "OneToOne-ChildName"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
var oneToOneEntityUpdatedEventTriggered = false;
var oneToOneAndOneToOneEntityUpdatedEventTriggered = false;
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToOne>>(async _ =>
{
oneToOneEntityUpdatedEventTriggered = true;
});
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToOneAndOneToOne>>(async _ =>
{
oneToOneAndOneToOneEntityUpdatedEventTriggered = true;
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne.ChildName = "ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
oneToOneEntityUpdatedEventTriggered.ShouldBeTrue();
oneToOneAndOneToOneEntityUpdatedEventTriggered.ShouldBeFalse();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
oneToOneEntityUpdatedEventTriggered = false;
oneToOneAndOneToOneEntityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne.OneToOne.ChildName = "OneToOne-ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
oneToOneEntityUpdatedEventTriggered.ShouldBeTrue();
oneToOneAndOneToOneEntityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToOne>>(_ =>
{
throw new Exception("Should not trigger this event");
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = null;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with one to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany = new List<AppEntityWithNavigationChildOneToMany>()
{
new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName1",
OneToMany = new List<AppEntityWithNavigationChildOneToManyAndOneToMany>()
{
new AppEntityWithNavigationChildOneToManyAndOneToMany()
{
ChildName = "OneToMany-ChildName1"
}
}
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
var oneToManyEntityUpdatedEventTriggered = false;
var oneToManyAndOneToManyEntityUpdatedEventTriggered = false;
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToMany>>(async _ =>
{
oneToManyEntityUpdatedEventTriggered = true;
});
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToManyAndOneToMany>>(async _ =>
{
oneToManyAndOneToManyEntityUpdatedEventTriggered = true;
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany[0].ChildName = "ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
oneToManyEntityUpdatedEventTriggered.ShouldBeTrue();
oneToManyAndOneToManyEntityUpdatedEventTriggered.ShouldBeFalse();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
oneToManyEntityUpdatedEventTriggered = false;
oneToManyAndOneToManyEntityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany[0].OneToMany[0].ChildName = "OneToMany-ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
oneToManyEntityUpdatedEventTriggered.ShouldBeTrue();
oneToManyAndOneToManyEntityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToMany>>(_ =>
{
throw new Exception("Should not trigger this event");
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany.Clear();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with many to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany = new List<AppEntityWithNavigationChildManyToMany>()
{
new AppEntityWithNavigationChildManyToMany
{
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany[0].ChildName = "ChildName2";
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany.Clear();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
}
[Fact]
public async Task Should_Trigger_Domain_Events_For_Aggregate_Root_When_EnsureCollectionLoaded_Navigation_Changes_Tests()
{
_loadEntityWithDetails = true;
var entityId = Guid.NewGuid();
await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity")

4
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs

@ -1,8 +1,8 @@
using Volo.Abp.Testing;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.EntityFrameworkCore;
public abstract class EntityFrameworkCoreTestBase : AbpIntegratedTest<AbpEntityFrameworkCoreTestModule>
public abstract class EntityFrameworkCoreTestBase : TestAppTestBase<AbpEntityFrameworkCoreTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{

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

@ -70,10 +70,22 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
modelBuilder.Entity<AppEntityWithNavigations>(b =>
{
b.ConfigureByConvention();
b.OwnsOne(v => v.AppEntityWithValueObjectAddress);
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(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
{
b.ConfigureByConvention();
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOneAndOneToOne>(x => x.Id);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToMany>(b =>
{
b.ConfigureByConvention();
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
}
}

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

@ -103,6 +103,18 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToOne>(b =>
{
b.ConfigureByConvention();
b.HasOne(x => x.OneToOne).WithOne().HasForeignKey<AppEntityWithNavigationChildOneToOneAndOneToOne>(x => x.Id);
});
modelBuilder.Entity<AppEntityWithNavigationChildOneToMany>(b =>
{
b.ConfigureByConvention();
b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationChildOneToManyId);
});
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

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

@ -36,7 +36,6 @@ public class AppEntityWithValueObjectAddress : ValueObject
{
public AppEntityWithValueObjectAddress(string country)
{
Country = country;
}
@ -48,8 +47,14 @@ public class AppEntityWithValueObjectAddress : ValueObject
}
}
public class AppEntityWithNavigationChildOneToOne : Entity<Guid>
{
public string ChildName { get; set; }
public virtual AppEntityWithNavigationChildOneToOneAndOneToOne OneToOne { get; set; }
}
public class AppEntityWithNavigationChildOneToOneAndOneToOne : Entity<Guid>
{
public string ChildName { get; set; }
}
@ -59,6 +64,15 @@ public class AppEntityWithNavigationChildOneToMany : Entity<Guid>
public Guid AppEntityWithNavigationId { get; set; }
public string ChildName { get; set; }
public virtual List<AppEntityWithNavigationChildOneToManyAndOneToMany> OneToMany { get; set; }
}
public class AppEntityWithNavigationChildOneToManyAndOneToMany : Entity<Guid>
{
public Guid AppEntityWithNavigationChildOneToManyId { get; set; }
public string ChildName { get; set; }
}
public class AppEntityWithNavigationChildManyToMany : AggregateRoot<Guid>

153
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/DomainEvents_Tests.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -176,155 +176,6 @@ public abstract class DomainEvents_Tests<TStartupModule> : TestAppTestBase<TStar
isDistributedEventTriggered.ShouldBeTrue();
}
[Fact]
public async Task Should_Trigger_Domain_Events_For_Aggregate_Root_When_Navigation_Changes_Tests()
{
var entityId = Guid.NewGuid();
await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity"));
var entityUpdatedEventTriggered = false;
var personCreatedEventCount = 0;
var entityUpdatedEventTriggerCount = 0;
LocalEventBus.Subscribe<EntityCreatedEventData<Person>>(data =>
{
personCreatedEventCount++;
return Task.CompletedTask;
});
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigations>>(async data =>
{
entityUpdatedEventTriggered = !entityUpdatedEventTriggered;
await PersonRepository.InsertAsync(new Person(Guid.NewGuid(), Guid.NewGuid().ToString(), new Random().Next(1, 100)));
});
// Test with simple property
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithValueObjectAddress>>(data =>
{
throw new Exception("Should not trigger this event");
});
// Test with value object
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey");
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress = null;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with one to one
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = new AppEntityWithNavigationChildOneToOne
{
ChildName = "ChildName"
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToOne>>(data =>
{
throw new Exception("Should not trigger this event");
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = null;
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with one to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany = new List<AppEntityWithNavigationChildOneToMany>()
{
new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigationChildOneToMany>>(data =>
{
throw new Exception("Should not trigger this event");
});
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany.Clear();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
// Test with many to many
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany = new List<AppEntityWithNavigationChildManyToMany>()
{
new AppEntityWithNavigationChildManyToMany
{
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
entityUpdatedEventTriggered = false;
await WithUnitOfWorkAsync(async () =>
{
var entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany.Clear();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
entityUpdatedEventTriggered.ShouldBeTrue();
personCreatedEventCount.ShouldBe(++entityUpdatedEventTriggerCount);
}
private class MyCustomEventData
{
public string Value { get; set; }
@ -368,7 +219,7 @@ public abstract class AbpEntityChangeOptions_DomainEvents_Tests<TStartupModule>
LocalEventBus.Subscribe<EntityUpdatedEventData<AppEntityWithNavigations>>(data =>
{
entityUpdatedEventTriggered = !entityUpdatedEventTriggered;
entityUpdatedEventTriggered = true;
return Task.CompletedTask;
});

Loading…
Cancel
Save