diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj
index dc168af444..5de64b7a1d 100644
--- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj
+++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo.Abp.TenantManagement.Domain.csproj
@@ -21,6 +21,7 @@
+
diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
index 764e9f7209..076c8b7a67 100644
--- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
+++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/AbpTenantManagementDomainModule.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
+using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
@@ -16,6 +17,7 @@ namespace Volo.Abp.TenantManagement
[DependsOn(typeof(AbpDataModule))]
[DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpAutoMapperModule))]
+ [DependsOn(typeof(AbpCachingModule))]
public class AbpTenantManagementDomainModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItem.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItem.cs
new file mode 100644
index 0000000000..94f898bbe5
--- /dev/null
+++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItem.cs
@@ -0,0 +1,36 @@
+using System;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.TenantManagement
+{
+ [Serializable]
+ [IgnoreMultiTenancy]
+ public class TenantCacheItem
+ {
+ private const string CacheKeyFormat = "i:{0},n:{1}";
+
+ public TenantConfiguration Value { get; set; }
+
+ public TenantCacheItem()
+ {
+
+ }
+
+ public TenantCacheItem(TenantConfiguration value)
+ {
+ Value = value;
+ }
+
+ public static string CalculateCacheKey(Guid? id, string name)
+ {
+ if (id == null && name.IsNullOrWhiteSpace())
+ {
+ throw new AbpException("Both id and name can't be invalid.");
+ }
+
+ return string.Format(CacheKeyFormat,
+ id?.ToString() ?? "null",
+ (name.IsNullOrWhiteSpace() ? "null" : name));
+ }
+ }
+}
diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItemInvalidator.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItemInvalidator.cs
new file mode 100644
index 0000000000..0416dd8df2
--- /dev/null
+++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantCacheItemInvalidator.cs
@@ -0,0 +1,24 @@
+using System.Threading.Tasks;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Entities.Events;
+using Volo.Abp.EventBus;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantCacheItemInvalidator : ILocalEventHandler>, ITransientDependency
+ {
+ protected IDistributedCache Cache { get; }
+
+ public TenantCacheItemInvalidator(IDistributedCache cache)
+ {
+ Cache = cache;
+ }
+
+ public virtual async Task HandleEventAsync(EntityChangedEventData eventData)
+ {
+ await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(eventData.Entity.Id, null));
+ await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(null, eventData.Entity.Name));
+ }
+ }
+}
diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs
index 00b9ef0963..54e07a55fb 100644
--- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs
+++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain/Volo/Abp/TenantManagement/TenantStore.cs
@@ -1,5 +1,7 @@
using System;
using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
@@ -13,73 +15,125 @@ namespace Volo.Abp.TenantManagement
protected ITenantRepository TenantRepository { get; }
protected IObjectMapper ObjectMapper { get; }
protected ICurrentTenant CurrentTenant { get; }
+ protected IDistributedCache Cache { get; }
public TenantStore(
ITenantRepository tenantRepository,
IObjectMapper objectMapper,
- ICurrentTenant currentTenant)
+ ICurrentTenant currentTenant,
+ IDistributedCache cache)
{
TenantRepository = tenantRepository;
ObjectMapper = objectMapper;
CurrentTenant = currentTenant;
+ Cache = cache;
}
public virtual async Task FindAsync(string name)
{
- using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
- {
- var tenant = await TenantRepository.FindByNameAsync(name);
- if (tenant == null)
- {
- return null;
- }
-
- return ObjectMapper.Map(tenant);
- }
+ return (await GetCacheItemAsync(null, name)).Value;
}
public virtual async Task FindAsync(Guid id)
{
- using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
- {
- var tenant = await TenantRepository.FindAsync(id);
- if (tenant == null)
- {
- return null;
- }
-
- return ObjectMapper.Map(tenant);
- }
+ return (await GetCacheItemAsync(id, null)).Value;
}
[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(string name)
{
- using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ return (GetCacheItem(null, name)).Value;
+ }
+
+ [Obsolete("Use FindAsync method.")]
+ public virtual TenantConfiguration Find(Guid id)
+ {
+ return (GetCacheItem(id, null)).Value;
+ }
+
+ protected virtual async Task GetCacheItemAsync(Guid? id, string name)
+ {
+ var cacheKey = CalculateCacheKey(id, name);
+
+ var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true);
+ if (cacheItem != null)
+ {
+ return cacheItem;
+ }
+
+ if (id.HasValue)
{
- var tenant = TenantRepository.FindByName(name);
- if (tenant == null)
+ using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
- return null;
+ var tenant = await TenantRepository.FindAsync(id.Value);
+ return await SetCacheAsync(cacheKey, tenant);
}
+ }
- return ObjectMapper.Map(tenant);
+ if (!name.IsNullOrWhiteSpace())
+ {
+ using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = await TenantRepository.FindByNameAsync(name);
+ return await SetCacheAsync(cacheKey, tenant);
+ }
}
+
+ throw new AbpException("Both id and name can't be invalid.");
}
- [Obsolete("Use FindAsync method.")]
- public virtual TenantConfiguration Find(Guid id)
+ protected virtual async Task SetCacheAsync(string cacheKey, [CanBeNull]Tenant tenant)
+ {
+ var tenantConfiguration = tenant != null ? ObjectMapper.Map(tenant) : null;
+ var cacheItem = new TenantCacheItem(tenantConfiguration);
+ await Cache.SetAsync(cacheKey, cacheItem, considerUow: true);
+ return cacheItem;
+ }
+
+ [Obsolete("Use GetCacheItemAsync method.")]
+ protected virtual TenantCacheItem GetCacheItem(Guid? id, string name)
{
- using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ var cacheKey = CalculateCacheKey(id, name);
+
+ var cacheItem = Cache.Get(cacheKey, considerUow: true);
+ if (cacheItem != null)
+ {
+ return cacheItem;
+ }
+
+ if (id.HasValue)
{
- var tenant = TenantRepository.FindById(id);
- if (tenant == null)
+ using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
- return null;
+ var tenant = TenantRepository.FindById(id.Value);
+ return SetCache(cacheKey, tenant);
}
+ }
- return ObjectMapper.Map(tenant);
+ if (!name.IsNullOrWhiteSpace())
+ {
+ using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
+ {
+ var tenant = TenantRepository.FindByName(name);
+ return SetCache(cacheKey, tenant);
+ }
}
+
+ throw new AbpException("Both id and name can't be invalid.");
+ }
+
+ [Obsolete("Use SetCacheAsync method.")]
+ protected virtual TenantCacheItem SetCache(string cacheKey, [CanBeNull]Tenant tenant)
+ {
+ var tenantConfiguration = tenant != null ? ObjectMapper.Map(tenant) : null;
+ var cacheItem = new TenantCacheItem(tenantConfiguration);
+ Cache.Set(cacheKey, cacheItem, considerUow: true);
+ return cacheItem;
+ }
+
+ protected virtual string CalculateCacheKey(Guid? id, string name)
+ {
+ return TenantCacheItem.CalculateCacheKey(id, name);
}
}
}
diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantCacheItemInvalidator_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantCacheItemInvalidator_Tests.cs
new file mode 100644
index 0000000000..4c2a8e3c3f
--- /dev/null
+++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.Domain.Tests/Volo/Abp/TenantManagement/TenantCacheItemInvalidator_Tests.cs
@@ -0,0 +1,86 @@
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Caching;
+using Volo.Abp.MultiTenancy;
+using Xunit;
+
+namespace Volo.Abp.TenantManagement
+{
+ public class TenantCacheItemInvalidator_Tests : AbpTenantManagementDomainTestBase
+ {
+ private readonly IDistributedCache _cache;
+ private readonly ITenantStore _tenantStore;
+ private readonly ITenantRepository _tenantRepository;
+
+ public TenantCacheItemInvalidator_Tests()
+ {
+ _cache = GetRequiredService>();
+ _tenantStore = GetRequiredService();
+ _tenantRepository = GetRequiredService();
+ }
+
+ [Fact]
+ public async Task Get_Tenant_Should_Cached()
+ {
+ var acme = await _tenantRepository.FindByNameAsync("acme");
+ acme.ShouldNotBeNull();
+
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldBeNull();
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldBeNull();
+
+ await _tenantStore.FindAsync(acme.Id);
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldNotBeNull();
+
+ await _tenantStore.FindAsync(acme.Name);
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldNotBeNull();
+
+
+ var volosoft = _tenantRepository.FindByName("volosoft");
+ volosoft.ShouldNotBeNull();
+
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldBeNull();
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldBeNull();
+
+ _tenantStore.Find(volosoft.Id);
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldNotBeNull();
+
+ _tenantStore.Find(volosoft.Name);
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldNotBeNull();
+ }
+
+ [Fact]
+ public async Task Cache_Should_Invalidator_When_Tenant_Changed()
+ {
+ var acme = await _tenantRepository.FindByNameAsync("acme");
+ acme.ShouldNotBeNull();
+
+ // FindAsync will cache tenant.
+ await _tenantStore.FindAsync(acme.Id);
+ await _tenantStore.FindAsync(acme.Name);
+
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldNotBeNull();
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldNotBeNull();
+
+ await _tenantRepository.DeleteAsync(acme);
+
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldBeNull();
+ (await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldBeNull();
+
+
+ var volosoft = await _tenantRepository.FindByNameAsync("volosoft");
+ volosoft.ShouldNotBeNull();
+
+ // Find will cache tenant.
+ _tenantStore.Find(volosoft.Id);
+ _tenantStore.Find(volosoft.Name);
+
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldNotBeNull();
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldNotBeNull();
+
+ await _tenantRepository.DeleteAsync(volosoft);
+
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldBeNull();
+ (_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldBeNull();
+ }
+ }
+}