Browse Source

Merge pull request #20012 from abpframework/ApplyAbpConceptsForModifiedEntity

Apply `AbpConcepts` to entities which navigation property changes.
pull/20186/head
Halil İbrahim Kalkan 2 years ago
committed by GitHub
parent
commit
351db42e37
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 28
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  2. 9
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
  3. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/EntityChange_Tests.cs
  4. 10
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/EntityChange_Tests.cs
  5. 3
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/AppEntityWithNavigations.cs
  6. 106
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityChange_Tests.cs

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

@ -203,6 +203,11 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{ {
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges) if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{ {
if (entityEntry.State == EntityState.Unchanged)
{
ApplyAbpConceptsForModifiedEntity(entityEntry, true);
}
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted) if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{ {
EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity); EntityChangeEventHelper.PublishEntityDeletedEvent(entityEntry.Entity);
@ -281,7 +286,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
} }
} }
private void PublishEntityEvents(EntityEventReport changeReport) protected virtual void PublishEntityEvents(EntityEventReport changeReport)
{ {
foreach (var localEvent in changeReport.DomainEvents) foreach (var localEvent in changeReport.DomainEvents)
{ {
@ -395,7 +400,6 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break; break;
case EntityState.Modified: case EntityState.Modified:
ApplyAbpConceptsForModifiedEntity(entry);
if (entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd))) if (entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{ {
if (entry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey())) if (entry.Properties.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey()))
@ -404,6 +408,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break; break;
} }
ApplyAbpConceptsForModifiedEntity(entry);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted) if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{ {
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity); EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
@ -413,8 +418,9 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity); EntityChangeEventHelper.PublishEntityUpdatedEvent(entry.Entity);
} }
} }
else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && AbpEfCoreNavigationHelper.IsEntityEntryModified(entry)) else if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges && AbpEfCoreNavigationHelper.IsNavigationEntryModified(entry))
{ {
ApplyAbpConceptsForModifiedEntity(entry, true);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted) if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{ {
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity); EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
@ -435,7 +441,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected virtual void HandlePropertiesBeforeSave() protected virtual void HandlePropertiesBeforeSave()
{ {
foreach (var entry in ChangeTracker.Entries().ToList()) var entries = ChangeTracker.Entries().ToList();
foreach (var entry in entries)
{ {
HandleExtraPropertiesOnSave(entry); HandleExtraPropertiesOnSave(entry);
@ -444,6 +451,14 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
UpdateConcurrencyStamp(entry); UpdateConcurrencyStamp(entry);
} }
} }
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{
foreach (var entry in AbpEfCoreNavigationHelper.GetChangedEntityEntries().Where(x => x.State == EntityState.Unchanged))
{
UpdateConcurrencyStamp(entry);
}
}
} }
protected virtual EntityEventReport CreateEventReport() protected virtual EntityEventReport CreateEventReport()
@ -572,9 +587,10 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
SetCreationAuditProperties(entry); SetCreationAuditProperties(entry);
} }
protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry) protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, bool forceApply = false)
{ {
if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd))) if (forceApply ||
entry.Properties.Any(x => x.IsModified && (x.Metadata.ValueGenerated == ValueGenerated.Never || x.Metadata.ValueGenerated == ValueGenerated.OnAdd)))
{ {
IncrementEntityVersionProperty(entry); IncrementEntityVersionProperty(entry);
SetModificationAuditProperties(entry); SetModificationAuditProperties(entry);

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

@ -167,7 +167,7 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return EntityEntries.TryGetValue(entryId, out var abpEntityEntry) && abpEntityEntry.IsModified; return EntityEntries.TryGetValue(entryId, out var abpEntityEntry) && abpEntityEntry.IsModified;
} }
public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, int navigationEntryIndex) public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, int? navigationEntryIndex = null)
{ {
var entryId = GetEntityEntryIdentity(entityEntry); var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null) if (entryId == null)
@ -180,7 +180,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return false; return false;
} }
var navigationEntryProperty = abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex); if (navigationEntryIndex == null)
{
return abpEntityEntry.NavigationEntries.Any(x => x.IsModified);
}
var navigationEntryProperty = abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex.Value);
return navigationEntryProperty != null && navigationEntryProperty.IsModified; return navigationEntryProperty != null && navigationEntryProperty.IsModified;
} }

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/EntityChange_Tests.cs

@ -0,0 +1,8 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.EntityFrameworkCore.Domain;
public class EntityChange_Tests : EntityChange_Tests<AbpEntityFrameworkCoreTestModule>
{
}

10
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/EntityChange_Tests.cs

@ -0,0 +1,10 @@
using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Domain;
[Collection(MongoTestCollection.Name)]
public class EntityChange_Tests : EntityChange_Tests<AbpMongoDbTestModule>
{
}

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

@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Domain.Values; using Volo.Abp.Domain.Values;
namespace Volo.Abp.TestApp.Domain; namespace Volo.Abp.TestApp.Domain;
public class AppEntityWithNavigations : AggregateRoot<Guid> public class AppEntityWithNavigations : FullAuditedAggregateRoot<Guid>
{ {
protected AppEntityWithNavigations() protected AppEntityWithNavigations()
{ {

106
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityChange_Tests.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.TestApp.Testing;
public abstract class EntityChange_Tests<TStartupModule> : TestAppTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<AppEntityWithNavigations, Guid> AppEntityWithNavigationsRepository;
protected EntityChange_Tests()
{
AppEntityWithNavigationsRepository = GetRequiredService<IRepository<AppEntityWithNavigations, Guid>>();
}
[Fact]
public async Task Should_Update_AbpConcepts_Properties_When_Entity_Or_Its_Navigation_Property_Changed()
{
var entityId = Guid.NewGuid();
var entity = await AppEntityWithNavigationsRepository.InsertAsync(new AppEntityWithNavigations(entityId, "TestEntity"));
var concurrencyStamp = entity.ConcurrencyStamp;
var lastModificationTime = entity.LastModificationTime;
// Test with simple property
await WithUnitOfWorkAsync(async () =>
{
entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.Name = Guid.NewGuid().ToString();
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
concurrencyStamp.ShouldNotBe(entity.ConcurrencyStamp);
lastModificationTime.ShouldNotBe(entity.LastModificationTime);
concurrencyStamp = entity.ConcurrencyStamp;
lastModificationTime = entity.LastModificationTime;
// Test with value object
await WithUnitOfWorkAsync(async () =>
{
entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.AppEntityWithValueObjectAddress = new AppEntityWithValueObjectAddress("Turkey");
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
concurrencyStamp.ShouldNotBe(entity.ConcurrencyStamp);
lastModificationTime.ShouldNotBe(entity.LastModificationTime);
concurrencyStamp = entity.ConcurrencyStamp;
lastModificationTime = entity.LastModificationTime;
// Test with one to one
await WithUnitOfWorkAsync(async () =>
{
entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToOne = new AppEntityWithNavigationChildOneToOne
{
ChildName = "ChildName"
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
concurrencyStamp.ShouldNotBe(entity.ConcurrencyStamp);
lastModificationTime.ShouldNotBe(entity.LastModificationTime);
concurrencyStamp = entity.ConcurrencyStamp;
lastModificationTime = entity.LastModificationTime;
// Test with one to many
await WithUnitOfWorkAsync(async () =>
{
entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.OneToMany = new List<AppEntityWithNavigationChildOneToMany>()
{
new AppEntityWithNavigationChildOneToMany
{
AppEntityWithNavigationId = entity.Id,
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
concurrencyStamp.ShouldNotBe(entity.ConcurrencyStamp);
lastModificationTime.ShouldNotBe(entity.LastModificationTime);
concurrencyStamp = entity.ConcurrencyStamp;
lastModificationTime = entity.LastModificationTime;
// Test with many to many
await WithUnitOfWorkAsync(async () =>
{
entity = await AppEntityWithNavigationsRepository.GetAsync(entityId);
entity.ManyToMany = new List<AppEntityWithNavigationChildManyToMany>()
{
new AppEntityWithNavigationChildManyToMany
{
ChildName = "ChildName1"
}
};
await AppEntityWithNavigationsRepository.UpdateAsync(entity);
});
concurrencyStamp.ShouldNotBe(entity.ConcurrencyStamp);
lastModificationTime.ShouldNotBe(entity.LastModificationTime);
}
}
Loading…
Cancel
Save