Browse Source

Merge pull request #14055 from abpframework/entity-cache

Distributed Entity Cache
pull/14221/head
Halil İbrahim Kalkan 4 years ago
committed by GitHub
parent
commit
18fb3b2069
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpDistributedCacheOptions.cs
  2. 5
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs
  3. 4
      framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj
  4. 10
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
  5. 70
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs
  6. 82
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs
  7. 40
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithObjectMapper.cs
  8. 22
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithObjectMapperContext.cs
  9. 24
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs
  10. 22
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/IEntityCache.cs
  11. 7
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/EntityCache_Tests.cs
  12. 5
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  13. 5
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  14. 7
      framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/DomainEvents/EntityCache_Tests.cs
  15. 6
      framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs
  16. 9
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/EntityCache_Tests.cs
  17. 3
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/ITestAppMongoDbContext.cs
  18. 3
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs
  19. 3
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Domain/TestAutoMapProfile.cs
  20. 11
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs
  21. 14
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs
  22. 93
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestMemoryDistributedCache.cs
  23. 120
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityCache_Tests.cs

15
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpDistributedCacheOptions.cs

@ -33,4 +33,19 @@ public class AbpDistributedCacheOptions
GlobalCacheEntryOptions = new DistributedCacheEntryOptions();
KeyPrefix = "";
}
public void ConfigureCache<TCacheItem>(DistributedCacheEntryOptions options)
{
ConfigureCache(typeof(TCacheItem), options);
}
public void ConfigureCache(Type cacheItemType, DistributedCacheEntryOptions options)
{
ConfigureCache(CacheNameAttribute.GetCacheName(cacheItemType), options);
}
public void ConfigureCache(string cacheName, DistributedCacheEntryOptions options)
{
CacheConfigurators.Add(name => cacheName != name ? null : options);
}
}

5
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs

@ -16,6 +16,11 @@ public class CacheNameAttribute : Attribute
Name = name;
}
public static string GetCacheName<TCacheItem>()
{
return GetCacheName(typeof(TCacheItem));
}
public static string GetCacheName(Type cacheItemType)
{
var cacheNameAttribute = cacheItemType

4
framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj

@ -16,16 +16,14 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Auditing\Volo.Abp.Auditing.csproj" />
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\Volo.Abp.Data\Volo.Abp.Data.csproj" />
<ProjectReference Include="..\Volo.Abp.EventBus\Volo.Abp.EventBus.csproj" />
<ProjectReference Include="..\Volo.Abp.ExceptionHandling\Volo.Abp.ExceptionHandling.csproj" />
<ProjectReference Include="..\Volo.Abp.Guids\Volo.Abp.Guids.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
<ProjectReference Include="..\Volo.Abp.Timing\Volo.Abp.Timing.csproj" />
<ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" />
</ItemGroup>
</Project>

10
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs

@ -1,17 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Specifications;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.Domain;
@ -20,13 +18,11 @@ namespace Volo.Abp.Domain;
typeof(AbpDataModule),
typeof(AbpEventBusModule),
typeof(AbpGuidsModule),
typeof(AbpMultiTenancyModule),
typeof(AbpThreadingModule),
typeof(AbpTimingModule),
typeof(AbpUnitOfWorkModule),
typeof(AbpObjectMappingModule),
typeof(AbpExceptionHandlingModule),
typeof(AbpSpecificationsModule)
typeof(AbpSpecificationsModule),
typeof(AbpCachingModule)
)]
public class AbpDddDomainModule : AbpModule
{

70
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs

@ -0,0 +1,70 @@
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
using Volo.Abp.Uow;
namespace Volo.Abp.Domain.Entities.Caching;
public abstract class EntityCacheBase<TEntity, TEntityCacheItem, TKey> :
IEntityCache<TEntityCacheItem, TKey>,
ILocalEventHandler<EntityChangedEventData<TEntity>>
where TEntity : Entity<TKey>
where TEntityCacheItem : class
{
protected IReadOnlyRepository<TEntity, TKey> Repository { get; }
protected IDistributedCache<TEntityCacheItem, TKey> Cache { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected EntityCacheBase(
IReadOnlyRepository<TEntity, TKey> repository,
IDistributedCache<TEntityCacheItem, TKey> cache,
IUnitOfWorkManager unitOfWorkManager)
{
Repository = repository;
Cache = cache;
UnitOfWorkManager = unitOfWorkManager;
}
public virtual async Task<TEntityCacheItem> FindAsync(TKey id)
{
return await Cache.GetOrAddAsync(
id,
async () => MapToCacheItem(await Repository.FindAsync(id))
);
}
public virtual async Task<TEntityCacheItem> GetAsync(TKey id)
{
return await Cache.GetOrAddAsync(
id,
async () => MapToCacheItem(await Repository.GetAsync(id))
);
}
protected abstract TEntityCacheItem MapToCacheItem(TEntity entity);
public async Task HandleEventAsync(EntityChangedEventData<TEntity> eventData)
{
if (eventData is EntityCreatedEventData<TEntity>)
{
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
* before the UOW completes.
* 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);
}
}
}

82
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheServiceCollectionExtensions.cs

@ -0,0 +1,82 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Caching;
namespace Volo.Abp.Domain.Entities.Caching;
public static class EntityCacheServiceCollectionExtensions
{
public static IServiceCollection AddEntityCache<TEntity, TKey>(
this IServiceCollection services,
[CanBeNull] DistributedCacheEntryOptions cacheOptions = null)
where TEntity : Entity<TKey>
{
services
.TryAddTransient<
IEntityCache<TEntity, TKey>,
EntityCacheWithoutCacheItem<TEntity, TKey>
>();
services
.TryAddTransient<EntityCacheWithoutCacheItem<TEntity, TKey>>();
services.Configure<AbpDistributedCacheOptions>(options =>
{
options.ConfigureCache<TEntity>(cacheOptions ?? GetDefaultCacheOptions());
});
return services;
}
public static IServiceCollection AddEntityCache<TEntity, TEntityCacheItem, TKey>(
this IServiceCollection services,
[CanBeNull] DistributedCacheEntryOptions cacheOptions = null)
where TEntity : Entity<TKey>
where TEntityCacheItem : class
{
services
.TryAddTransient<
IEntityCache<TEntityCacheItem, TKey>,
EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>
>();
services
.TryAddTransient<EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>>();
services.Configure<AbpDistributedCacheOptions>(options =>
{
options.ConfigureCache<TEntityCacheItem>(cacheOptions ?? GetDefaultCacheOptions());
});
return services;
}
public static IServiceCollection AddEntityCache<TObjectMapperContext, TEntity, TEntityCacheItem, TKey>(
this IServiceCollection services,
[CanBeNull] DistributedCacheEntryOptions cacheOptions = null)
where TEntity : Entity<TKey>
where TEntityCacheItem : class
{
services
.TryAddTransient<
IEntityCache<TEntityCacheItem, TKey>,
EntityCacheWithObjectMapperContext<TObjectMapperContext, TEntity, TEntityCacheItem, TKey>
>();
services.TryAddTransient<EntityCacheWithObjectMapperContext<TObjectMapperContext, TEntity, TEntityCacheItem, TKey>>();
services.Configure<AbpDistributedCacheOptions>(options =>
{
options.ConfigureCache<TEntityCacheItem>(cacheOptions ?? GetDefaultCacheOptions());
});
return services;
}
private static DistributedCacheEntryOptions GetDefaultCacheOptions()
{
return new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)
};
}
}

40
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithObjectMapper.cs

@ -0,0 +1,40 @@
using System;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Domain.Entities.Caching;
public class EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey> :
EntityCacheBase<TEntity, TEntityCacheItem, TKey>
where TEntity : Entity<TKey>
where TEntityCacheItem : class
{
protected IObjectMapper ObjectMapper { get; }
public EntityCacheWithObjectMapper(
IReadOnlyRepository<TEntity, TKey> repository,
IDistributedCache<TEntityCacheItem, TKey> cache,
IObjectMapper objectMapper)
: base(
repository,
cache)
{
ObjectMapper = objectMapper;
}
protected override TEntityCacheItem MapToCacheItem(TEntity entity)
{
if (entity == null)
{
return null;
}
if (typeof(TEntity) == typeof(TEntityCacheItem))
{
return entity.As<TEntityCacheItem>();
}
return ObjectMapper.Map<TEntity, TEntityCacheItem>(entity);
}
}

22
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithObjectMapperContext.cs

@ -0,0 +1,22 @@
using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Domain.Entities.Caching;
public class EntityCacheWithObjectMapperContext<TObjectMapperContext, TEntity, TEntityCacheItem, TKey> :
EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>
where TEntity : Entity<TKey>
where TEntityCacheItem : class
{
public EntityCacheWithObjectMapperContext(
IReadOnlyRepository<TEntity, TKey> repository,
IDistributedCache<TEntityCacheItem, TKey> cache,
IObjectMapper<TObjectMapperContext> objectMapper) // Intentionally injected with TContext
: base(
repository,
cache,
objectMapper)
{
}
}

24
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheWithoutCacheItem.cs

@ -0,0 +1,24 @@
using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Domain.Entities.Caching;
public class EntityCacheWithoutCacheItem<TEntity, TKey> :
EntityCacheBase<TEntity, TEntity, TKey>
where TEntity : Entity<TKey>
{
public EntityCacheWithoutCacheItem(
IReadOnlyRepository<TEntity, TKey> repository,
IDistributedCache<TEntity, TKey> cache)
: base(
repository,
cache)
{
}
protected override TEntity MapToCacheItem(TEntity entity)
{
return entity;
}
}

22
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/IEntityCache.cs

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Domain.Entities.Caching;
public interface IEntityCache<TEntityCacheItem, in TKey>
where TEntityCacheItem : class
{
/// <summary>
/// Gets the entity with given <paramref name="id"/>,
/// or returns null if the entity was not found.
/// </summary>
[ItemCanBeNull]
Task<TEntityCacheItem> FindAsync(TKey id);
/// <summary>
/// Gets the entity with given <paramref name="id"/>,
/// or throws <see cref="EntityNotFoundException"/> if the entity was not found.
/// </summary>
[ItemNotNull]
Task<TEntityCacheItem> GetAsync(TKey id);
}

7
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/EntityCache_Tests.cs

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

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

@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext;
using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.EntityFrameworkCore;
@ -19,6 +20,8 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
public DbSet<EntityWithIntPk> EntityWithIntPks { get; set; }
public DbSet<Author> Author { get; set; }
public DbSet<Product> Products { get; set; }
public TestMigrationsDbContext(DbContextOptions<TestMigrationsDbContext> options)
: base(options)
@ -51,5 +54,7 @@ public class TestMigrationsDbContext : AbpDbContext<TestMigrationsDbContext>
d.HasKey(x => new { x.CityId, x.Name });
});
});
modelBuilder.Entity<Product>();
}
}

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

@ -7,6 +7,7 @@ using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.TestApp.FourthContext;
using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp.EntityFrameworkCore;
@ -28,6 +29,8 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
public DbSet<Author> Author { get; set; }
public DbSet<FourthDbContextDummyEntity> FourthDummyEntities { get; set; }
public DbSet<Product> Products { get; set; }
public TestAppDbContext(DbContextOptions<TestAppDbContext> options)
: base(options)
@ -79,6 +82,8 @@ public class TestAppDbContext : AbpDbContext<TestAppDbContext>, IThirdDbContext,
b.ApplyObjectExtensionMappings();
});
modelBuilder.Entity<Product>();
modelBuilder.TryConfigureObjectExtensions<TestAppDbContext>();
}
}

7
framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/DomainEvents/EntityCache_Tests.cs

@ -0,0 +1,7 @@
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.MemoryDb.DomainEvents;
public class EntityCache_Tests : EntityCache_Tests<AbpMemoryDbTestModule>
{
}

6
framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/TestApp/MemoryDb/TestAppMemoryDbContext.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using Volo.Abp.MemoryDb;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp.MemoryDb;
@ -9,8 +10,9 @@ public class TestAppMemoryDbContext : MemoryDbContext
{
private static readonly Type[] EntityTypeList = {
typeof(Person),
typeof(EntityWithIntPk)
};
typeof(EntityWithIntPk),
typeof(Product)
};
public override IReadOnlyList<Type> GetEntityTypes()
{

9
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Domain/EntityCache_Tests.cs

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

3
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/ITestAppMongoDbContext.cs

@ -2,6 +2,7 @@
using Volo.Abp.Data;
using Volo.Abp.MongoDB;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp.MongoDB;
@ -11,4 +12,6 @@ public interface ITestAppMongoDbContext : IAbpMongoDbContext
IMongoCollection<Person> People { get; }
IMongoCollection<City> Cities { get; }
IMongoCollection<Product> Products { get; }
}

3
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/TestAppMongoDbContext.cs

@ -6,6 +6,7 @@ using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.TestApp.FourthContext;
using Volo.Abp.MongoDB.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp.MongoDB;
@ -23,6 +24,8 @@ public class TestAppMongoDbContext : AbpMongoDbContext, ITestAppMongoDbContext,
public IMongoCollection<ThirdDbContextDummyEntity> DummyEntities => Collection<ThirdDbContextDummyEntity>();
public IMongoCollection<FourthDbContextDummyEntity> FourthDummyEntities => Collection<FourthDbContextDummyEntity>();
public IMongoCollection<Product> Products => Collection<Product>();
protected internal override void CreateModel(IMongoModelBuilder modelBuilder)
{

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

@ -1,4 +1,5 @@
using AutoMapper;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp.Domain;
@ -7,5 +8,7 @@ public class TestAutoMapProfile : Profile
public TestAutoMapProfile()
{
CreateMap<PersonEto, Person>().ReverseMap();
CreateMap<Product, ProductCacheItem>();
}
}

11
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestAppModule.cs

@ -1,11 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Application;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain.Entities.Caching;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.TestApp.Application.Dto;
using Volo.Abp.TestApp.Testing;
using Volo.Abp.Threading;
namespace Volo.Abp.TestApp;
@ -22,6 +27,10 @@ public class TestAppModule : AbpModule
{
ConfigureAutoMapper();
ConfigureDistributedEventBus();
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, TestMemoryDistributedCache>());
context.Services.AddEntityCache<Product, Guid>();
context.Services.AddEntityCache<Product, ProductCacheItem, Guid>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

14
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.TestApp.Testing;
namespace Volo.Abp.TestApp;
@ -15,19 +16,24 @@ public class TestDataBuilder : ITransientDependency
public static Guid IstanbulCityId { get; } = new Guid("4d734a0e-3e6b-4bad-bb43-ef8cf1b09633");
public static Guid LondonCityId { get; } = new Guid("27237527-605e-4652-a2a5-68e0e512da36");
public static Guid ProductId { get; } = new Guid("c5b3fbd4-7b0b-4a72-8d99-ccf6c8c98f61");
private readonly IBasicRepository<Person, Guid> _personRepository;
private readonly ICityRepository _cityRepository;
private readonly IRepository<EntityWithIntPk, int> _entityWithIntPksRepository;
private readonly IRepository<Product, Guid> _productRepository;
public TestDataBuilder(
IBasicRepository<Person, Guid> personRepository,
ICityRepository cityRepository,
IRepository<EntityWithIntPk, int> entityWithIntPksRepository)
IRepository<EntityWithIntPk, int> entityWithIntPksRepository,
IRepository<Product, Guid> productRepository)
{
_personRepository = personRepository;
_cityRepository = cityRepository;
_entityWithIntPksRepository = entityWithIntPksRepository;
_productRepository = productRepository;
}
public async Task BuildAsync()
@ -35,6 +41,7 @@ public class TestDataBuilder : ITransientDependency
await AddCities();
await AddPeople();
await AddEntitiesWithPks();
await AddProducts();
}
private async Task AddCities()
@ -87,4 +94,9 @@ public class TestDataBuilder : ITransientDependency
{
await _entityWithIntPksRepository.InsertAsync(new EntityWithIntPk("Entity1"));
}
private async Task AddProducts()
{
await _productRepository.InsertAsync(new Product(ProductId, "Product1", decimal.One));
}
}

93
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestMemoryDistributedCache.cs

@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TestApp;
[DisableConventionalRegistration]
public class TestMemoryDistributedCache : MemoryDistributedCache, ICacheSupportsMultipleItems
{
public TestMemoryDistributedCache(IOptions<MemoryDistributedCacheOptions> optionsAccessor)
: base(optionsAccessor)
{
}
public TestMemoryDistributedCache(IOptions<MemoryDistributedCacheOptions> optionsAccessor, ILoggerFactory loggerFactory)
: base(optionsAccessor, loggerFactory)
{
}
public byte[][] GetMany(IEnumerable<string> keys)
{
var values = new List<byte[]>();
foreach (var key in keys)
{
values.Add(Get(key));
}
return values.ToArray();
}
public async Task<byte[][]> GetManyAsync(IEnumerable<string> keys, CancellationToken token = default)
{
var values = new List<byte[]>();
foreach (var key in keys)
{
values.Add(await GetAsync(key, token));
}
return values.ToArray();
}
public void SetMany(IEnumerable<KeyValuePair<string, byte[]>> items, DistributedCacheEntryOptions options)
{
foreach (var item in items)
{
Set(item.Key, item.Value, options);
}
}
public async Task SetManyAsync(IEnumerable<KeyValuePair<string, byte[]>> items, DistributedCacheEntryOptions options, CancellationToken token = default)
{
foreach (var item in items)
{
await SetAsync(item.Key, item.Value, options, token);
}
}
public void RefreshMany(IEnumerable<string> keys)
{
foreach (var key in keys)
{
Refresh(key);
}
}
public async Task RefreshManyAsync(IEnumerable<string> keys, CancellationToken token = default)
{
foreach (var key in keys)
{
await RefreshAsync(key, token);
}
}
public void RemoveMany(IEnumerable<string> keys)
{
foreach (var key in keys)
{
Remove(key);
}
}
public async Task RemoveManyAsync(IEnumerable<string> keys, CancellationToken token = default)
{
foreach (var key in keys)
{
await RemoveAsync(key, token);
}
}
}

120
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/EntityCache_Tests.cs

@ -0,0 +1,120 @@
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Domain.Entities.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.TestApp.Testing;
public abstract class EntityCache_Tests<TStartupModule> : TestAppTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly IRepository<Product, Guid> ProductRepository;
protected readonly IEntityCache<Product, Guid> ProductEntityCache;
protected readonly IEntityCache<ProductCacheItem, Guid> ProductCacheItem;
protected EntityCache_Tests()
{
ProductRepository = GetRequiredService<IRepository<Product, Guid>>();
ProductEntityCache = GetRequiredService<IEntityCache<Product, Guid>>();
ProductCacheItem = GetRequiredService<IEntityCache<ProductCacheItem, Guid>>();
}
[Fact]
public async Task Should_Return_Null_IF_Entity_Not_Exist()
{
var notExistId = Guid.NewGuid();
(await ProductEntityCache.FindAsync(notExistId)).ShouldBeNull();
(await ProductCacheItem.FindAsync(notExistId)).ShouldBeNull();
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_IF_Entity_Not_Exist()
{
var notExistId = Guid.NewGuid();
await Assert.ThrowsAsync<EntityNotFoundException>(() => ProductEntityCache.GetAsync(notExistId));
await Assert.ThrowsAsync<EntityNotFoundException>(() => ProductCacheItem.GetAsync(notExistId));
}
[Fact]
public async Task Should_Return_EntityCache()
{
var product = await ProductEntityCache.FindAsync(TestDataBuilder.ProductId);
product.ShouldNotBeNull();
product.Id.ShouldBe(TestDataBuilder.ProductId);
product.Name.ShouldBe("Product1");
product.Price.ShouldBe(decimal.One);
var productCacheItem = await ProductCacheItem.FindAsync(product.Id);
productCacheItem.ShouldNotBeNull();
productCacheItem.Id.ShouldBe(TestDataBuilder.ProductId);
productCacheItem.Name.ShouldBe("Product1");
productCacheItem.Price.ShouldBe(decimal.One);
}
[Fact]
public async Task Should_Return_Null_IF_Deleted()
{
await ProductRepository.DeleteAsync(TestDataBuilder.ProductId);
(await ProductEntityCache.FindAsync(TestDataBuilder.ProductId)).ShouldBeNull();
(await ProductCacheItem.FindAsync(TestDataBuilder.ProductId)).ShouldBeNull();
}
[Fact]
public async Task Should_Return_New_EntityCache_IF_Updated()
{
(await ProductEntityCache.FindAsync(TestDataBuilder.ProductId)).ShouldNotBeNull();
(await ProductCacheItem.FindAsync(TestDataBuilder.ProductId)).ShouldNotBeNull();
var product = await ProductRepository.FindAsync(TestDataBuilder.ProductId);
product.Name = "Product2";
product.Price = decimal.Zero;
await ProductRepository.UpdateAsync(product);
product = await ProductEntityCache.FindAsync(product.Id);
product.ShouldNotBeNull();
product.Id.ShouldBe(TestDataBuilder.ProductId);
product.Name.ShouldBe("Product2");
product.Price.ShouldBe(decimal.Zero);
var productCacheItem = await ProductCacheItem.FindAsync(product.Id);
productCacheItem.ShouldNotBeNull();
productCacheItem.Id.ShouldBe(TestDataBuilder.ProductId);
productCacheItem.Name.ShouldBe("Product2");
productCacheItem.Price.ShouldBe(decimal.Zero);
}
}
[Serializable]
public class Product : FullAuditedAggregateRoot<Guid>
{
public Product(Guid id, string name, decimal price)
: base(id)
{
Name = name;
Price = price;
}
[JsonInclude]
public override Guid Id { get; protected set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
[Serializable]
public class ProductCacheItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Loading…
Cancel
Save