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 (entityEntry.State == EntityState.Unchanged)
{
ApplyAbpConceptsForModifiedEntity(entityEntry, true);
}
if (entityEntry.Entity is ISoftDelete && entityEntry.Entity.As<ISoftDelete>().IsDeleted)
{
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)
{
@ -395,7 +400,6 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break;
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.Where(x => x.IsModified).All(x => x.Metadata.IsForeignKey()))
@ -404,6 +408,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
break;
}
ApplyAbpConceptsForModifiedEntity(entry);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
@ -413,8 +418,9 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
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)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity);
@ -435,7 +441,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected virtual void HandlePropertiesBeforeSave()
{
foreach (var entry in ChangeTracker.Entries().ToList())
var entries = ChangeTracker.Entries().ToList();
foreach (var entry in entries)
{
HandleExtraPropertiesOnSave(entry);
@ -444,6 +451,14 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
UpdateConcurrencyStamp(entry);
}
}
if (EntityChangeOptions.Value.PublishEntityUpdatedEventWhenNavigationChanges)
{
foreach (var entry in AbpEfCoreNavigationHelper.GetChangedEntityEntries().Where(x => x.State == EntityState.Unchanged))
{
UpdateConcurrencyStamp(entry);
}
}
}
protected virtual EntityEventReport CreateEventReport()
@ -572,9 +587,10 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
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);
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;
}
public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, int navigationEntryIndex)
public virtual bool IsNavigationEntryModified(EntityEntry entityEntry, int? navigationEntryIndex = null)
{
var entryId = GetEntityEntryIdentity(entityEntry);
if (entryId == null)
@ -180,7 +180,12 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
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;
}

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.Collections.Generic;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Domain.Values;
namespace Volo.Abp.TestApp.Domain;
public class AppEntityWithNavigations : AggregateRoot<Guid>
public class AppEntityWithNavigations : FullAuditedAggregateRoot<Guid>
{
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