diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index f5394dd93b..53de9eb8cd 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -753,6 +753,8 @@ public static class QADbContextModelCreatingExtensions > If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` and `ConfigureEfCoreEntity` methods. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. +> The `Object Extension` feature need the `Change Tracking`, which means you can't use the read-only repositories for the entities that have `extension properties(MapEfCoreProperty)`, Please see the [Repositories documentation](Repositories.md) to learn the change tracking behavior. + See the "*ConfigureByConvention Method*" section above for more information. ## Advanced Topics diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs index 2cb2b2c32b..62d0b5304e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; using Volo.Abp.Caching; +using Volo.Abp.Data; using Volo.Abp.Domain.Entities.Events; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus; +using Volo.Abp.ObjectExtending; using Volo.Abp.Uow; namespace Volo.Abp.Domain.Entities.Caching; @@ -26,32 +28,52 @@ public abstract class EntityCacheBase : Cache = cache; UnitOfWorkManager = unitOfWorkManager; } - + public virtual async Task FindAsync(TKey id) { return await Cache.GetOrAddAsync( id, - async () => MapToCacheItem(await Repository.FindAsync(id))! - ); + async () => + { + if (HasObjectExtensionInfo()) + { + Repository.EnableTracking(); + } + + return MapToCacheItem(await Repository.FindAsync(id))!; + }); } public virtual async Task GetAsync(TKey id) { return (await Cache.GetOrAddAsync( id, - async () => MapToCacheItem(await Repository.GetAsync(id))! - ))!; + async () => + { + if (HasObjectExtensionInfo()) + { + Repository.EnableTracking(); + } + + return MapToCacheItem(await Repository.GetAsync(id))!; + }))!; + } + + protected virtual bool HasObjectExtensionInfo() + { + return typeof(IHasExtraProperties).IsAssignableFrom(typeof(TEntity)) && + ObjectExtensionManager.Instance.GetOrNull(typeof(TEntity)) != null; } protected abstract TEntityCacheItem? MapToCacheItem(TEntity? entity); - + public async Task HandleEventAsync(EntityChangedEventData eventData) { if (eventData is EntityCreatedEventData) { return; } - + /* Why we are using double remove: * First Cache.RemoveAsync drops the cache item in a unit of work. * Some other application / thread may read the value from database and put it to the cache again @@ -59,12 +81,12 @@ public abstract class EntityCacheBase : * The second Cache.RemoveAsync drops the cache item after the database transaction is complete. * Only the second Cache.RemoveAsync may not be enough if the application crashes just after the UOW completes. */ - + await Cache.RemoveAsync(eventData.Entity.Id); - + if(UnitOfWorkManager.Current != null) { await Cache.RemoveAsync(eventData.Entity.Id, considerUow: true); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs index c208c7b107..d55ef92439 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs @@ -16,13 +16,8 @@ public static class EntityCacheServiceCollectionExtensions DistributedCacheEntryOptions? cacheOptions = null) where TEntity : Entity { - services - .TryAddTransient< - IEntityCache, - EntityCacheWithoutCacheItem - >(); - services - .TryAddTransient>(); + services.TryAddTransient, EntityCacheWithoutCacheItem>(); + services.TryAddTransient>(); services.Configure(options => { @@ -43,13 +38,8 @@ public static class EntityCacheServiceCollectionExtensions where TEntity : Entity where TEntityCacheItem : class { - services - .TryAddTransient< - IEntityCache, - EntityCacheWithObjectMapper - >(); - services - .TryAddTransient>(); + services.TryAddTransient, EntityCacheWithObjectMapper>(); + services.TryAddTransient>(); services.Configure(options => { @@ -65,11 +55,7 @@ public static class EntityCacheServiceCollectionExtensions where TEntity : Entity where TEntityCacheItem : class { - services - .TryAddTransient< - IEntityCache, - EntityCacheWithObjectMapperContext - >(); + services.TryAddTransient, EntityCacheWithObjectMapperContext>(); services.TryAddTransient>(); services.Configure(options => diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs index 0f300a7f28..125273173e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs @@ -20,6 +20,4 @@ public class EntityCacheWithoutCacheItem : { return entity; } - - }