diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index f99d9130bd..6a5b608c76 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -686,6 +686,12 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, return; } + string? concurrencyStamp = null; + if (entry.Entity is IHasConcurrencyStamp hasConcurrencyStamp) + { + concurrencyStamp = hasConcurrencyStamp.ConcurrencyStamp; + } + ExtraPropertyDictionary? originalExtraProperties = null; if (entry.Entity is IHasExtraProperties) { @@ -694,6 +700,11 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, entry.Reload(); + if (concurrencyStamp != null && entry.Entity is IHasConcurrencyStamp) + { + ObjectHelper.TrySetProperty(entry.Entity.As(), x => x.ConcurrencyStamp, () => concurrencyStamp); + } + if (entry.Entity is IHasExtraProperties) { ObjectHelper.TrySetProperty(entry.Entity.As(), x => x.ExtraProperties, () => originalExtraProperties); diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs index 3e08c63417..484d627cb4 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using Volo.Abp.Data; using Volo.Abp.Domain.Entities; namespace Volo.Abp.Domain.Repositories.MemoryDb; @@ -37,10 +38,21 @@ public class MemoryDatabaseCollection : IMemoryDatabaseCollection(); + if (entity is IHasConcurrencyStamp hasConcurrencyStamp && originalEntity is IHasConcurrencyStamp originalHasConcurrencyStamp) + { + if (hasConcurrencyStamp.ConcurrencyStamp != originalHasConcurrencyStamp.ConcurrencyStamp) + { + throw new AbpDbConcurrencyException("Database operation expected to affect 1 row but actually affected 0 row. Data may have been modified or deleted since entities were loaded. This exception has been thrown on optimistic concurrency check."); + } + } + + _dictionary[GetEntityKey(entity)] = _memoryDbSerializer.Serialize(entity); } public void Remove(TEntity entity) diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs index 1f403ade62..4de3b867bc 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/SoftDelete_Tests.cs @@ -139,4 +139,24 @@ public abstract class SoftDelete_Tests : TestAppTestBase(async () => + { + await PersonRepository.DeleteAsync(douglas); + }); + + douglas = await PersonRepository.GetAsync(TestDataBuilder.UserDouglasId); + douglas.ConcurrencyStamp.ShouldNotBeNull(); + douglas.ChangeName("Changed Name"); + + // Try again with the correct ConcurrencyStamp will be not throw exception + await PersonRepository.DeleteAsync(douglas); + } }