From b49790ed88dd03f2f83f9697b93e3d589e555ecc Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 17 Jun 2026 16:58:53 +0800 Subject: [PATCH] feat: Optimize dynamic localization initialization --- .../DynamicLocalizationChangedEto.cs | 5 + .../DynamicLocalizationInitializerEto.cs | 53 +++ .../ResourceChangedEto.cs | 11 + .../AbpLocalizationManagementDomainModule.cs | 1 + .../AbpLocalizationManagementOptions.cs | 4 + .../DynamicLocalizationChangedEventHandler.cs | 51 +++ ...DynamicLocalizationInitializerCacheItem.cs | 73 ++++ ...micLocalizationInitializerEeventHandler.cs | 379 ++++++++++++++++++ .../ExternalLocalizationStoreCache.cs | 112 +----- .../ExternalLocalizationTextStoreCache.cs | 71 +--- .../IExternalLocalizationStoreCache.cs | 2 - .../IExternalLocalizationTextStoreCache.cs | 2 - .../LocalizationResourceCacheInvalidator.cs | 36 -- .../External/LocalizationResourceCacheItem.cs | 5 + .../IResourceRepository.cs | 3 + .../LocalizationLanguageCacheInvalidator.cs | 21 - .../LocalizationLanguageStoreCache.cs | 51 +-- .../LocalizationTextCacheInvalidator.cs | 32 -- .../LocalizationTextStoreCache.cs | 61 +-- .../StaticLocalizationSaver.cs | 344 +++------------- .../EfCoreResourceRepository.cs | 7 + 21 files changed, 682 insertions(+), 642 deletions(-) create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs create mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs delete mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheInvalidator.cs delete mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs delete mode 100644 aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs new file mode 100644 index 000000000..1463c9bf3 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs @@ -0,0 +1,5 @@ +namespace LINGYUN.Abp.LocalizationManagement; + +public class DynamicLocalizationChangedEto +{ +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs new file mode 100644 index 000000000..1d6fc5dab --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs @@ -0,0 +1,53 @@ +namespace LINGYUN.Abp.LocalizationManagement; + +public abstract class DynamicLocalizationInitializerEto +{ + public string[] Keys { get; set; } + protected DynamicLocalizationInitializerEto() + { + + } + + protected DynamicLocalizationInitializerEto(string[] keys) + { + Keys = keys; + } +} + +public class DynamicLanguageInitializerEto : DynamicLocalizationInitializerEto +{ + public DynamicLanguageInitializerEto() + { + + } + + public DynamicLanguageInitializerEto(string[] keys) : base(keys) + { + } +} + + +public class DynamicResourceInitializerEto : DynamicLocalizationInitializerEto +{ + public DynamicResourceInitializerEto() + { + + } + + public DynamicResourceInitializerEto(string[] keys) : base(keys) + { + } +} + + +public class DynamicTextInitializerEto : DynamicLocalizationInitializerEto +{ + public DynamicTextInitializerEto() + { + + } + + public DynamicTextInitializerEto(string[] keys) : base(keys) + { + } +} \ No newline at end of file diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs new file mode 100644 index 000000000..a12b250b3 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.LocalizationManagement; + +[Serializable] +public class ResourceChangedEto +{ + public List Resources { get; set; } + public List Cultures { get; set; } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs index 9bcb740ca..306129ae6 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs @@ -20,6 +20,7 @@ namespace LINGYUN.Abp.LocalizationManagement; public class AbpLocalizationManagementDomainModule : AbpModule { private readonly CancellationTokenSource _cancellationTokenSource = new(); + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddMapperlyObjectMapper(); diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs index c9fe5f701..ebf73618f 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs @@ -6,6 +6,10 @@ public class AbpLocalizationManagementOptions /// 保存本地化文本到数据库 /// public bool SaveStaticLocalizationsToDatabase { get; set; } = true; + /// + /// 是否动态本地化文本初始化节点 + /// + public bool IsDynamicLocalizationInitializerHost { get; set; } = false; public AbpLocalizationManagementOptions() { diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs new file mode 100644 index 000000000..9eee09c4c --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs @@ -0,0 +1,51 @@ +using LINGYUN.Abp.LocalizationManagement.External; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class DynamicLocalizationChangedEventHandler : + IDistributedEventHandler, + ITransientDependency +{ + protected IDistributedCache ResourcesCache { get; } + protected IDistributedCache LanguageCache { get; } + + protected IExternalLocalizationTextStoreCache ExternalLocalizationTextStoreCache { get; } + protected ILocalizationTextStoreCache LocalizationTextStoreCache { get; } + + public DynamicLocalizationChangedEventHandler( + IDistributedCache resourcesCache, + IDistributedCache languageCache, + IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache, + ILocalizationTextStoreCache localizationTextStoreCache) + { + ResourcesCache = resourcesCache; + LanguageCache = languageCache; + ExternalLocalizationTextStoreCache = externalLocalizationTextStoreCache; + LocalizationTextStoreCache = localizationTextStoreCache; + } + + public async virtual Task HandleEventAsync(DynamicLocalizationChangedEto eventData) + { + var resourcesCacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey); + var languagesCacheItem = await LanguageCache.GetAsync(LocalizationLanguageCacheItem.CacheKey); + + foreach (var language in languagesCacheItem.Languages) + { + foreach (var resource in resourcesCacheItem.Resources) + { + if (ExternalLocalizationTextStoreCache is ExternalLocalizationTextStoreCache storeCache) + { + await storeCache.UpdateCache(resource.Name, language.CultureName); + } + if (LocalizationTextStoreCache is LocalizationTextStoreCache textStoreCache) + { + await textStoreCache.UpdateCache(resource.Name, language.CultureName); + } + } + } + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs new file mode 100644 index 000000000..b9325f9b8 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Caching; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.LocalizationManagement; + +[Serializable] +[IgnoreMultiTenancy] +[CacheName("DynamicLanguageInitializer")] +public class DynamicLanguageInitializerCacheItem +{ + public string CultureName { get; set; } + public string UiCultureName { get; set; } + public string DisplayName { get; set; } + public string TwoLetterISOLanguageName { get; set; } + public DynamicLanguageInitializerCacheItem() + { + } + public DynamicLanguageInitializerCacheItem( + string cultureName, + string uiCultureName, + string displayName, + string twoLetterISOLanguageName) + { + CultureName = cultureName; + UiCultureName = uiCultureName; + DisplayName = displayName; + TwoLetterISOLanguageName = twoLetterISOLanguageName; + } +} + +[Serializable] +[IgnoreMultiTenancy] +[CacheName("DynamicResourceInitializer")] +public class DynamicResourceInitializerCacheItem +{ + public string ResourceName { get; set; } + public string DefaultCultureName { get; set; } + public DynamicResourceInitializerCacheItem() + { + } + public DynamicResourceInitializerCacheItem( + string resourceName, + string defaultCultureName) + { + ResourceName = resourceName; + DefaultCultureName = defaultCultureName; + } +} + +[Serializable] +[IgnoreMultiTenancy] +[CacheName("DynamicTextInitializer")] +public class DynamicTextInitializerCacheItem +{ + public string ResourceName { get; set; } + public string CultureName { get; set; } + public Dictionary Texts { get; set; } + public DynamicTextInitializerCacheItem() + { + Texts = new Dictionary(); + } + public DynamicTextInitializerCacheItem( + string resourceName, + string cultureName, + Dictionary texts) + { + ResourceName = resourceName; + CultureName = cultureName; + Texts = texts; + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs new file mode 100644 index 000000000..ae2bf0377 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs @@ -0,0 +1,379 @@ +using LINGYUN.Abp.LocalizationManagement.External; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; +using Volo.Abp.Localization; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class DynamicLocalizationInitializerEeventHandler : + IDistributedEventHandler, + IDistributedEventHandler, + IDistributedEventHandler, + ITransientDependency +{ + public ILogger Logger { protected get; set; } + + protected IDistributedCache ResourceCache { get; } + protected IDistributedCache ResourcesCache { get; } + protected IDistributedCache LanguageCache { get; } + protected IDistributedCache LocalizationTextCache { get; } + + protected IDistributedCache DynamicLanguageCache { get; } + protected IDistributedCache DynamicResourceCache { get; } + protected IDistributedCache DynamicTextCache { get; } + + protected AbpLocalizationManagementOptions LocalizationManagementOptions { get; } + + protected IGuidGenerator GuidGenerator { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + + protected ILanguageRepository LanguageRepository { get; } + protected IResourceRepository ResourceRepository { get; } + protected ITextRepository TextRepository { get; } + + public DynamicLocalizationInitializerEeventHandler( + IDistributedCache resourceCache, + IDistributedCache resourcesCache, + IDistributedCache languageCache, + IDistributedCache localizationTextCache, + IDistributedCache dynamicLanguageCache, + IDistributedCache dynamicResourceCache, + IDistributedCache dynamicTextCache, + IOptions localizationManagementOptions, + IGuidGenerator guidGenerator, + IAbpDistributedLock distributedLock, + IDistributedEventBus distributedEventBus, + IUnitOfWorkManager unitOfWorkManager, + ILanguageRepository languageRepository, + IResourceRepository resourceRepository, + ITextRepository textRepository) + { + ResourceCache = resourceCache; + ResourcesCache = resourcesCache; + LanguageCache = languageCache; + LocalizationTextCache = localizationTextCache; + DynamicLanguageCache = dynamicLanguageCache; + DynamicResourceCache = dynamicResourceCache; + DynamicTextCache = dynamicTextCache; + LocalizationManagementOptions = localizationManagementOptions.Value; + GuidGenerator = guidGenerator; + DistributedLock = distributedLock; + DistributedEventBus = distributedEventBus; + UnitOfWorkManager = unitOfWorkManager; + LanguageRepository = languageRepository; + ResourceRepository = resourceRepository; + TextRepository = textRepository; + + Logger = NullLogger.Instance; + } + + public async virtual Task HandleEventAsync(DynamicLanguageInitializerEto eventData) + { + if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase || + !LocalizationManagementOptions.IsDynamicLocalizationInitializerHost) + { + return; + } + + Logger.LogDebug("Waiting to acquire the distributed lock for saving static languages..."); + + await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( + $"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicLanguageInitializerEto)}", + TimeSpan.FromSeconds(5)); + if (applicationLockHandle == null) + { + throw new AbpException("Failed to acquire the initialization lock for the localization languages!"); + } + + using var unitOfWork = UnitOfWorkManager.Begin(true, true); + + var cacheItems = await DynamicLanguageCache.GetManyAsync(eventData.Keys); + + await SaveLanguagesAsync(cacheItems.Select(kv => kv.Value).ToArray()); + + Logger.LogInformation("Refresh language cache items."); + await RefreshLanguagesCacheAsync(); + + await DynamicLanguageCache.RemoveManyAsync(eventData.Keys); + + await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto()); + + await unitOfWork.CompleteAsync(); + + Logger.LogInformation("Completed to save static languages."); + } + + public async virtual Task HandleEventAsync(DynamicResourceInitializerEto eventData) + { + if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase || + !LocalizationManagementOptions.IsDynamicLocalizationInitializerHost) + { + return; + } + + Logger.LogDebug("Waiting to acquire the distributed lock for saving static resources..."); + + await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( + $"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicResourceInitializerEto)}", + TimeSpan.FromSeconds(5)); + if (applicationLockHandle == null) + { + throw new AbpException("Failed to acquire the initialization lock for the localization resources!"); + } + + using var unitOfWork = UnitOfWorkManager.Begin(true, true); + + var cacheItems = await DynamicResourceCache.GetManyAsync(eventData.Keys); + + await SaveResourcesAsync(cacheItems.Select(kv => kv.Value).ToArray()); + + Logger.LogInformation("Refresh resource cache items."); + await RefreshResourcesCacheAsync(); + + await DynamicResourceCache.RemoveManyAsync(eventData.Keys); + + await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto()); + + await unitOfWork.CompleteAsync(); + + Logger.LogInformation("Completed to save static resources."); + } + + public async virtual Task HandleEventAsync(DynamicTextInitializerEto eventData) + { + if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase || + !LocalizationManagementOptions.IsDynamicLocalizationInitializerHost) + { + return; + } + + Logger.LogDebug("Waiting to acquire the distributed lock for saving static texts..."); + + await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( + $"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicTextInitializerEto)}", + TimeSpan.FromSeconds(5)); + if (applicationLockHandle == null) + { + throw new AbpException("Failed to acquire the initialization lock for the localization texts!"); + } + + using var unitOfWork = UnitOfWorkManager.Begin(true, true); + + var cacheItemDics = await DynamicTextCache.GetManyAsync(eventData.Keys); + var cacheItems = cacheItemDics.Select(kv => kv.Value).ToArray(); + + await SaveTextsAsync(cacheItems); + + Logger.LogInformation("Refresh text cache items."); + await RefreshTextsCacheAsync(cacheItems); + + await DynamicResourceCache.RemoveManyAsync(eventData.Keys); + + await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto()); + + await unitOfWork.CompleteAsync(); + + Logger.LogInformation("Completed to save static texts."); + } + + private async Task SaveLanguagesAsync(DynamicLanguageInitializerCacheItem[] cacheItems) + { + var newLanguages = new List(); + var updateLanguages = new List(); + var existsLanguages = await LanguageRepository.GetListAsync(); + foreach (var cacheItem in cacheItems) + { + var existsLanguage = existsLanguages.FirstOrDefault(x => x.CultureName == cacheItem.CultureName); + if (existsLanguage == null) + { + newLanguages.Add( + new Language( + GuidGenerator.Create(), + cacheItem.CultureName, + cacheItem.UiCultureName, + cacheItem.DisplayName, + cacheItem.TwoLetterISOLanguageName)); + } + else + { + var isUpdated = false; + if (existsLanguage.DisplayName != cacheItem.DisplayName) + { + isUpdated = true; + existsLanguage.SetDisplayName(cacheItem.DisplayName); + } + if (existsLanguage.TwoLetterISOLanguageName != cacheItem.TwoLetterISOLanguageName) + { + isUpdated = true; + existsLanguage.SetTwoLetterISOLanguageName(cacheItem.TwoLetterISOLanguageName); + } + if (isUpdated) + { + updateLanguages.Add(existsLanguage); + } + } + } + + if (newLanguages.Count > 0) + { + Logger.LogInformation("Saved {0} new languages.", newLanguages.Count); + await LanguageRepository.InsertManyAsync(newLanguages, autoSave: true); + } + + if (updateLanguages.Count > 0) + { + Logger.LogInformation("Update {0} changed languages.", updateLanguages.Count); + await LanguageRepository.UpdateManyAsync(updateLanguages, autoSave: true); + } + } + + private async Task RefreshLanguagesCacheAsync() + { + var languages = await LanguageRepository.GetListAsync(); + + var languageCacheItem = new LocalizationLanguageCacheItem( + languages.Select(x => + new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName)) + .ToList()); + + await LanguageCache.SetAsync(LocalizationLanguageCacheItem.CacheKey, languageCacheItem); + } + + private async Task SaveResourcesAsync(DynamicResourceInitializerCacheItem[] cacheItems) + { + var newResources = new List(); + var updateResources = new List(); + var existsResources = await ResourceRepository.GetListAsync(); + + foreach (var cacheItem in cacheItems) + { + var existsResource = existsResources.FirstOrDefault(x => x.Name == cacheItem.ResourceName); + if (existsResource == null) + { + newResources.Add( + new Resource( + GuidGenerator.Create(), + cacheItem.ResourceName, + cacheItem.ResourceName, + cacheItem.ResourceName, + cacheItem.DefaultCultureName)); + } + else if (existsResource.DefaultCultureName != cacheItem.DefaultCultureName) + { + existsResource.SetDefaultCultureName(cacheItem.DefaultCultureName); + updateResources.Add(existsResource); + } + } + + if (newResources.Count > 0) + { + Logger.LogInformation("Saved {0} new resources.", newResources.Count); + await ResourceRepository.InsertManyAsync(newResources, autoSave: true); + } + + if (updateResources.Count > 0) + { + Logger.LogInformation("Update {0} changed resources.", updateResources.Count); + await ResourceRepository.UpdateManyAsync(updateResources, autoSave: true); + } + } + + private async Task RefreshResourcesCacheAsync() + { + var resources = await ResourceRepository.GetListAsync(); + + var allResourceCacheItems = new List(); + var allResourceCacheItemDics = new Dictionary(); + foreach (var resource in resources) + { + var resourceCacheItem = new LocalizationResourceCacheItem(resource.Name, resource.DefaultCultureName) + { + IsEnabled = resource.Enable + }; + allResourceCacheItems.Add(resourceCacheItem); + allResourceCacheItemDics[resource.Name] = resourceCacheItem; + } + + await ResourcesCache.SetAsync( + LocalizationResourcesCacheItem.CacheKey, + new LocalizationResourcesCacheItem(allResourceCacheItems)); + + await ResourceCache.SetManyAsync(allResourceCacheItemDics); + } + + private async Task SaveTextsAsync(DynamicTextInitializerCacheItem[] cacheItems) + { + var newTexts = new List(); + var updateTexts = new List(); + + foreach (var cacheItem in cacheItems) + { + var existsTexts = await TextRepository.GetListAsync( + cacheItem.ResourceName, + cacheItem.CultureName); + + foreach (var text in cacheItem.Texts) + { + var existsText = existsTexts.FirstOrDefault(x => x.Key == text.Key); + if (existsText == null) + { + newTexts.Add( + new Text( + cacheItem.ResourceName, + cacheItem.CultureName, + text.Key, + text.Value)); + } + else if (existsText.Value != text.Value) + { + existsText.SetValue(text.Value); + updateTexts.Add(existsText); + } + } + } + + if (newTexts.Count > 0) + { + Logger.LogInformation("Saved {0} new texts.", newTexts.Count); + await TextRepository.InsertManyAsync(newTexts, autoSave: true); + } + + if (updateTexts.Count > 0) + { + Logger.LogInformation("Update {0} changed texts.", updateTexts.Count); + await TextRepository.UpdateManyAsync(updateTexts, autoSave: true); + } + } + + private async Task RefreshTextsCacheAsync(DynamicTextInitializerCacheItem[] cacheItems) + { + foreach (var cacheItem in cacheItems) + { + var setTexts = new Dictionary(); + var allTexts = await TextRepository.GetListAsync(cacheItem.ResourceName, cacheItem.CultureName); + foreach (var text in allTexts) + { + setTexts[text.Key] = text.Value; + } + + var textCacheKey = LocalizationTextCacheItem.CalculateCacheKey(cacheItem.ResourceName, cacheItem.CultureName); + var textCacheItem = new LocalizationTextCacheItem(cacheItem.ResourceName, cacheItem.CultureName, setTexts); + + await LocalizationTextCache.SetAsync(textCacheKey, textCacheItem); + } + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs index c6fd8ef3d..3cc690608 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs @@ -1,10 +1,8 @@ using Microsoft.Extensions.Options; -using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; using Volo.Abp.Localization; namespace LINGYUN.Abp.LocalizationManagement.External; @@ -14,32 +12,20 @@ public class ExternalLocalizationStoreCache : IExternalLocalizationStoreCache, I protected IDistributedCache ResourceCache { get; } protected IDistributedCache ResourcesCache { get; } protected AbpLocalizationOptions LocalizationOptions { get; } - protected IResourceRepository ResourceRepository { get; } - protected IAbpDistributedLock DistributedLock { get; } public ExternalLocalizationStoreCache( IDistributedCache resourcesCache, IDistributedCache resourceCache, - IOptions localizationOptions, - IResourceRepository resourceRepository, - IAbpDistributedLock distributedLock) + IOptions localizationOptions) { ResourceCache = resourceCache; ResourcesCache = resourcesCache; - ResourceRepository = resourceRepository; - DistributedLock = distributedLock; LocalizationOptions = localizationOptions.Value; } - public async virtual Task RemoveAsync(string resourceName) - { - await ResourceCache.RemoveAsync(resourceName); - await ResourcesCache.RemoveAsync(LocalizationResourcesCacheItem.CacheKey); - } - public async virtual Task GetResourceNamesAsync() { - var cacheItem = await GetResourcesCacheItemAsync(); + var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey); return cacheItem.Resources .Where(x => x.IsEnabled) @@ -57,108 +43,18 @@ public class ExternalLocalizationStoreCache : IExternalLocalizationStoreCache, I public async virtual Task GetResourceOrNullAsync(string resourceName) { - var cacheItem = await GetResourceCacheItemAsync(resourceName); + var cacheItem = await ResourceCache.GetAsync(resourceName); return cacheItem?.IsEnabled == true ? cacheItem : null; } public async virtual Task GetResourcesAsync() { - var cacheItem = await GetResourcesCacheItemAsync(); + var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey); return cacheItem?.Resources .Where(x => x.IsEnabled) .Where(x => !LocalizationOptions.Resources.ContainsKey(x.Name)) .ToArray(); } - - internal async Task UpdateCache(string resourceName) - { - if (resourceName.IsNullOrWhiteSpace()) - { - await CreateResourcesCacheItemAsync(); - } - else - { - await CreateResourceCacheItemAsync(resourceName); - } - } - - protected async virtual Task GetResourceCacheItemAsync(string resourceName) - { - var cacheItem = await ResourceCache.GetAsync(resourceName); - if (cacheItem != null) - { - return cacheItem; - } - - return await CreateResourceCacheItemAsync(resourceName); - } - - protected async virtual Task CreateResourceCacheItemAsync(string resourceName) - { - await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationStoreCache)}_{nameof(CreateResourceCacheItemAsync)}"); - - if (handle == null) - { - return null; - } - - LocalizationResourceCacheItem cacheItem; - var resource = await ResourceRepository.FindByNameAsync(resourceName); - if (resource == null) - { - cacheItem = new LocalizationResourceCacheItem(resourceName) - { - IsEnabled = false - }; - } - else - { - cacheItem = new LocalizationResourceCacheItem(resource.Name, resource.DefaultCultureName) - { - IsEnabled = resource.Enable - }; - } - - await ResourceCache.SetAsync(resourceName, cacheItem); - - return cacheItem; - } - - protected async virtual Task GetResourcesCacheItemAsync() - { - var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey); - if (cacheItem != null) - { - return cacheItem; - } - - return await CreateResourcesCacheItemAsync(); - } - - protected async virtual Task CreateResourcesCacheItemAsync() - { - await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationStoreCache)}_{nameof(CreateResourcesCacheItemAsync)}"); - - if (handle == null) - { - return null; - } - - var resources = await ResourceRepository.GetListAsync(); - var resourceItems = resources - .Where(x => !LocalizationOptions.Resources.ContainsKey(x.Name)) - .Select(x => new LocalizationResourceCacheItem(x.Name, x.DefaultCultureName) - { - IsEnabled = x.Enable, - }) - .ToList(); - - var cacheItem = new LocalizationResourcesCacheItem(resourceItems); - - await ResourcesCache.SetAsync(LocalizationResourcesCacheItem.CacheKey, cacheItem); - - return cacheItem; - } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs index e5f796c73..37a72f62a 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs @@ -1,11 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; using Volo.Abp.Localization; using Volo.Abp.Threading; @@ -14,43 +12,19 @@ namespace LINGYUN.Abp.LocalizationManagement.External; public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStoreCache, ISingletonDependency { protected ConcurrentDictionary MemoryCache { get; } - - protected IAbpDistributedLock DistributedLock { get; } - protected IServiceScopeFactory ServiceScopeFactory { get; } protected IDistributedCache DistributedCache { get; } protected IDistributedCache StampCache { get; } public ExternalLocalizationTextStoreCache( - IAbpDistributedLock distributedLock, - IServiceScopeFactory serviceScopeFactory, IDistributedCache distributedCache, IDistributedCache stampCache) { - DistributedLock = distributedLock; - ServiceScopeFactory = serviceScopeFactory; DistributedCache = distributedCache; StampCache = stampCache; MemoryCache = new ConcurrentDictionary(); } - public async virtual Task RemoveAsync(string resourceName, string cultureName) - { - var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName); - var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resourceName, cultureName); - - await using var lockHandle = await DistributedLock.TryAcquireAsync(cacheKey); - - if (lockHandle == null) - { - return; - } - - await DistributedCache.RemoveAsync(cacheKey); - - await StampCache.SetAsync(stampCacheKey, new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString())); - } - public Dictionary GetTexts(LocalizationResourceBase resource, string cultureName) { var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); @@ -97,7 +71,7 @@ public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStore return distributeCacheItem.Texts; } - distributeCacheItem = await CreateCacheItemAsync(resource, cultureName); + distributeCacheItem = await GetAndRefreshMemoryCacheItemAsync(resource.ResourceName, cultureName); if (distributeCacheItem == null) { @@ -114,41 +88,30 @@ public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStore internal async Task UpdateCache(LocalizationResourceBase resource, string cultureName) { - await CreateCacheItemAsync(resource, cultureName); + await GetAndRefreshMemoryCacheItemAsync(resource.ResourceName, cultureName); } - protected async virtual Task CreateCacheItemAsync(LocalizationResourceBase resource, string cultureName) + internal async Task UpdateCache(string resourceName, string cultureName) { - await using var lockHandle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationTextStoreCache)}_{nameof(CreateCacheItemAsync)}"); + await GetAndRefreshMemoryCacheItemAsync(resourceName, cultureName); + } - if (lockHandle == null) + protected async virtual Task GetAndRefreshMemoryCacheItemAsync(string resourceName, string cultureName) + { + var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName); + var cacheItem = await DistributedCache.GetAsync(cacheKey); + if (cacheItem != null) { - return null; - } - - var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); - var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); + var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resourceName, cultureName); - using var scope = ServiceScopeFactory.CreateScope(); - var repo = scope.ServiceProvider.GetRequiredService(); + var stampCacheItem = new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString()); + await StampCache.SetAsync(stampCacheKey, stampCacheItem); - var texts = await repo.GetListAsync(resource.ResourceName, cultureName); + MemoryCache[cacheKey] = new LocalizationTextMemoryCacheItem(cacheItem.Texts, stampCacheItem.Stamp); - var fillTexts = new Dictionary(); - - foreach (var text in texts) - { - fillTexts[text.Key] = text.Value; + return cacheItem; } - var cacheItem = new ExternalLocalizationTextCacheItem(fillTexts); - await DistributedCache.SetAsync(cacheKey, cacheItem); - - var stampCacheItem = new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString()); - await StampCache.SetAsync(stampCacheKey, stampCacheItem); - - MemoryCache[cacheKey] = new LocalizationTextMemoryCacheItem(cacheItem.Texts, stampCacheItem.Stamp); - - return cacheItem; + return null; } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs index cc3825d69..37622b100 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs @@ -11,6 +11,4 @@ public interface IExternalLocalizationStoreCache Task GetResourceNamesAsync(); Task GetResourcesAsync(); - - Task RemoveAsync(string resourceName); } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs index 3ad00d885..7c1c918e6 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs @@ -9,6 +9,4 @@ public interface IExternalLocalizationTextStoreCache Dictionary GetTexts(LocalizationResourceBase resource, string cultureName); Task> GetTextsAsync(LocalizationResourceBase resource, string cultureName); - - Task RemoveAsync(string resourceName, string cultureName); } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheInvalidator.cs deleted file mode 100644 index cb8cbe422..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheInvalidator.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events; -using Volo.Abp.EventBus; - -namespace LINGYUN.Abp.LocalizationManagement.External; - -public class LocalizationResourceCacheInvalidator : - ILocalEventHandler>, - ITransientDependency -{ - private readonly IExternalLocalizationTextStoreCache _externalLocalizationTextStoreCache; - private readonly ILocalizationLanguageStoreCache _localizationLanguageStoreCache; - private readonly IExternalLocalizationStoreCache _externalLocalizationStoreCache; - public LocalizationResourceCacheInvalidator( - IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache, - ILocalizationLanguageStoreCache localizationLanguageStoreCache, - IExternalLocalizationStoreCache externalLocalizationStoreCache) - { - _externalLocalizationTextStoreCache = externalLocalizationTextStoreCache; - _localizationLanguageStoreCache = localizationLanguageStoreCache; - _externalLocalizationStoreCache = externalLocalizationStoreCache; - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - await _externalLocalizationStoreCache.RemoveAsync(eventData.Entity.Name); -; - var languages = await _localizationLanguageStoreCache.GetLanguagesAsync(); - - foreach (var language in languages) - { - await _externalLocalizationTextStoreCache.RemoveAsync(eventData.Entity.Name, language.CultureName); - } - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs index 1bd160c3a..db983834a 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs @@ -18,6 +18,11 @@ public class LocalizationResourceCacheItem public bool IsEnabled { get; set; } + public LocalizationResourceCacheItem() + { + + } + public LocalizationResourceCacheItem( string name, string defaultCulture = null, diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IResourceRepository.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IResourceRepository.cs index 29a82b9dc..feb27fc82 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IResourceRepository.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IResourceRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; @@ -16,4 +17,6 @@ public interface IResourceRepository : IRepository Task FindByNameAsync( string name, CancellationToken cancellationToken = default); + + Task> GetActivedListAsync(CancellationToken cancellationToken = default); } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs deleted file mode 100644 index 70ae35136..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events; -using Volo.Abp.EventBus; - -namespace LINGYUN.Abp.LocalizationManagement; - -public class LocalizationLanguageCacheInvalidator: ILocalEventHandler>, ITransientDependency -{ - private readonly IDistributedCache _localizationLanguageCache; - public LocalizationLanguageCacheInvalidator(IDistributedCache localizationLanguageCache) - { - _localizationLanguageCache = localizationLanguageCache; - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - await _localizationLanguageCache.RemoveAsync(LocalizationLanguageCacheItem.CacheKey); - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs index 62427f03e..769782084 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs @@ -1,12 +1,9 @@ using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.Domain.ChangeTracking; using Volo.Abp.Localization; namespace LINGYUN.Abp.LocalizationManagement; @@ -14,64 +11,20 @@ namespace LINGYUN.Abp.LocalizationManagement; public class LocalizationLanguageStoreCache : ILocalizationLanguageStoreCache, ITransientDependency { protected IDistributedCache LanguageCache { get; } - protected ILanguageRepository LanguageRepository { get; } - protected IAbpDistributedLock DistributedLock { get; } protected AbpLocalizationOptions Options { get; } public LocalizationLanguageStoreCache( - IDistributedCache languageCache, - ILanguageRepository languageRepository, - IAbpDistributedLock distributedLock, + IDistributedCache languageCache, IOptions options) { LanguageCache = languageCache; - LanguageRepository = languageRepository; - DistributedLock = distributedLock; Options = options.Value; } - [DisableEntityChangeTracking] public async virtual Task> GetLanguagesAsync() - { - var cacheItem = await GetCacheItemAsync(); - - return cacheItem?.Languages.ToImmutableList() ?? [..Options.Languages]; - } - - internal async Task UpdateCache() - { - await CreateCacheItemAsync(); - } - - protected async virtual Task GetCacheItemAsync() { var cacheItem = await LanguageCache.GetAsync(LocalizationLanguageCacheItem.CacheKey); - if (cacheItem != null) - { - return cacheItem; - } - return await CreateCacheItemAsync(); - } - - protected async virtual Task CreateCacheItemAsync() - { - await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(LocalizationLanguageStoreCache)}_{nameof(GetCacheItemAsync)}"); - - if (handle == null) - { - return null; - } - - var languages = await LanguageRepository.GetListAsync(); - - var cacheItem = new LocalizationLanguageCacheItem( - languages.Select(x => - new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName)) - .ToList()); - - await LanguageCache.SetAsync(LocalizationLanguageCacheItem.CacheKey, cacheItem); - - return cacheItem; + return cacheItem?.Languages.ToImmutableList() ?? [..Options.Languages]; } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs deleted file mode 100644 index 5d845e4f8..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using LINGYUN.Abp.LocalizationManagement.External; -using System.Threading.Tasks; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events; -using Volo.Abp.EventBus; - -namespace LINGYUN.Abp.LocalizationManagement; - -public class LocalizationTextCacheInvalidator : ILocalEventHandler>, ITransientDependency -{ - private readonly IDistributedCache _localizationTextCache; - private readonly IExternalLocalizationTextStoreCache _externalLocalizationTextStoreCache; - public LocalizationTextCacheInvalidator( - IDistributedCache localizationTextCache, - IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache) - { - _externalLocalizationTextStoreCache = externalLocalizationTextStoreCache; - _localizationTextCache = localizationTextCache; - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - var cacheKey = LocalizationTextCacheItem.CalculateCacheKey( - eventData.Entity.ResourceName, - eventData.Entity.CultureName); - - await _localizationTextCache.RemoveAsync(cacheKey); - - await _externalLocalizationTextStoreCache.RemoveAsync(eventData.Entity.ResourceName, eventData.Entity.CultureName); - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs index 9fb076a36..9dd0414a4 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs @@ -1,12 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Localization; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.Domain.Repositories; using Volo.Abp.Localization; using Volo.Abp.Threading; @@ -16,17 +13,10 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto { private static readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1); private static readonly LocalizationResourceDictionary _staticCache = new LocalizationResourceDictionary(); - - protected IAbpDistributedLock DistributedLock { get; } - protected IServiceScopeFactory ServiceScopeFactory { get; } protected IDistributedCache LocalizationTextCache { get; } public LocalizationTextStoreCache( - IAbpDistributedLock distributedLock, - IServiceScopeFactory serviceScopeFactory, IDistributedCache localizationTextCache) { - DistributedLock = distributedLock; - ServiceScopeFactory = serviceScopeFactory; LocalizationTextCache = localizationTextCache; } @@ -44,7 +34,7 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) { - var cacheItem = await GetCacheItemAsync(resource, cultureName); + var cacheItem = await GetCacheItemAsync(resource.ResourceName, cultureName); if (cacheItem != null) { foreach (var text in cacheItem.Texts) @@ -66,14 +56,19 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto } internal async Task UpdateCache(LocalizationResourceBase resource, string cultureName) + { + await UpdateCache(resource.ResourceName, cultureName); + } + + internal async Task UpdateCache(string resourceName, string cultureName) { using (await _cacheLock.LockAsync()) { - var cacheItem = await GetCacheItemAsync(resource, cultureName); + var cacheItem = await GetCacheItemAsync(resourceName, cultureName); if (cacheItem != null) { var textDic = _staticCache - .GetOrAdd(resource.ResourceName, _ => new LocalizationCultureDictionary()) + .GetOrAdd(resourceName, _ => new LocalizationCultureDictionary()) .GetOrAdd(cultureName, _ => new LocalizationTextDictionary()); foreach (var text in cacheItem.Texts) @@ -84,41 +79,9 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto } } - protected async virtual Task GetCacheItemAsync(LocalizationResourceBase resource, string cultureName) + protected async virtual Task GetCacheItemAsync(string resourceName, string cultureName) { - // 异步本地化函数不受影响 - var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); - var cacheItem = await LocalizationTextCache.GetAsync(cacheKey); - if (cacheItem != null) - { - return cacheItem; - } - - await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(LocalizationTextStoreCache)}_{nameof(GetCacheItemAsync)}"); - - if (handle == null) - { - return null; - } - - var setTexts = new Dictionary(); - using (var scope = ServiceScopeFactory.CreateScope()) - { - var repo = scope.ServiceProvider.GetRequiredService(); - var texts = await repo.GetListAsync(resource.ResourceName, cultureName); - using (repo.DisableTracking()) - { - foreach (var text in texts) - { - setTexts[text.Key] = text.Value; - } - } - } - - cacheItem = new LocalizationTextCacheItem(resource.ResourceName, cultureName, setTexts); - - await LocalizationTextCache.SetAsync(cacheKey, cacheItem); - - return cacheItem; + var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName); + return await LocalizationTextCache.GetAsync(cacheKey); } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs index 2172b1a77..b08ac4410 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs @@ -1,87 +1,46 @@ -using LINGYUN.Abp.LocalizationManagement.External; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Text.Json; using System.Threading.Tasks; -using Volo.Abp; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.Guids; +using Volo.Abp.EventBus.Distributed; using Volo.Abp.Localization; -using Volo.Abp.Threading; -using Volo.Abp.Uow; namespace LINGYUN.Abp.LocalizationManagement; public class StaticLocalizationSaver : IStaticLocalizationSaver, ITransientDependency { protected ILogger Logger { get; } - - protected IDistributedCache Cache { get; } - protected IGuidGenerator GuidGenerator { get; } - protected IAbpDistributedLock DistributedLock { get; } - protected IUnitOfWorkManager UnitOfWorkManager { get; } - protected AbpDistributedCacheOptions CacheOptions { get; } - protected IApplicationInfoAccessor ApplicationInfoAccessor { get; } - protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IDistributedEventBus DistributedEventBus { get; } protected IStringLocalizerFactory StringLocalizerFactory { get; } protected AbpLocalizationOptions LocalizationOptions { get; } protected AbpLocalizationManagementOptions LocalizationManagementOptions { get; } - - protected IExternalLocalizationTextStoreCache ExternalLocalizationTextStoreCache { get; } - protected IExternalLocalizationStoreCache ExternalLocalizationStoreCache { get; } - protected ILocalizationLanguageStoreCache LocalizationLanguageStoreCache { get; } - protected ILocalizationTextStoreCache LocalizationTextStoreCache { get; } - - protected ILanguageRepository LanguageRepository { get; } - protected IResourceRepository ResourceRepository { get; } - protected ITextRepository TextRepository { get; } + protected IDistributedCache DynamicLanguageCache { get; } + protected IDistributedCache DynamicResourceCache { get; } + protected IDistributedCache DynamicTextCache { get; } public StaticLocalizationSaver( ILogger logger, - IDistributedCache cache, - IGuidGenerator guidGenerator, - IAbpDistributedLock distributedLock, - IUnitOfWorkManager unitOfWorkManager, - IApplicationInfoAccessor applicationInfoAccessor, - ICancellationTokenProvider cancellationTokenProvider, - IStringLocalizerFactory stringLocalizerFactory, - IOptions cacheOptions, - IOptions localizationOptions, - IOptions localizationManagementOptions, - IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache, - IExternalLocalizationStoreCache externalLocalizationStoreCache, - ILocalizationLanguageStoreCache localizationLanguageStoreCache, - ILocalizationTextStoreCache localizationTextStoreCache, - ILanguageRepository languageRepository, - IResourceRepository resourceRepository, - ITextRepository textRepository) + IDistributedEventBus distributedEventBus, + IStringLocalizerFactory stringLocalizerFactory, + IOptions localizationOptions, + IOptions localizationManagementOptions, + IDistributedCache dynamicLanguageCache, + IDistributedCache dynamicResourceCache, + IDistributedCache dynamicTextCache) { Logger = logger; - Cache = cache; - GuidGenerator = guidGenerator; - DistributedLock = distributedLock; - UnitOfWorkManager = unitOfWorkManager; - CacheOptions = cacheOptions.Value; - ApplicationInfoAccessor = applicationInfoAccessor; - CancellationTokenProvider = cancellationTokenProvider; + DistributedEventBus = distributedEventBus; StringLocalizerFactory = stringLocalizerFactory; LocalizationOptions = localizationOptions.Value; LocalizationManagementOptions = localizationManagementOptions.Value; - ExternalLocalizationTextStoreCache = externalLocalizationTextStoreCache; - ExternalLocalizationStoreCache = externalLocalizationStoreCache; - LocalizationLanguageStoreCache = localizationLanguageStoreCache; - LocalizationTextStoreCache = localizationTextStoreCache; - LanguageRepository = languageRepository; - ResourceRepository = resourceRepository; - TextRepository = textRepository; + DynamicLanguageCache = dynamicLanguageCache; + DynamicResourceCache = dynamicResourceCache; + DynamicTextCache = dynamicTextCache; } public async virtual Task SaveAsync() @@ -91,265 +50,72 @@ public class StaticLocalizationSaver : IStaticLocalizationSaver, ITransientDepen return; } - Logger.LogDebug("Waiting to acquire the distributed lock for saving static localizations..."); - - await using var applicationLockHandle = await DistributedLock.TryAcquireAsync( - GetApplicationDistributedLockKey(), - TimeSpan.FromSeconds(5)); - if (applicationLockHandle == null) - { - return; - } - - using var unitOfWork = UnitOfWorkManager.Begin(true, true); - try - { - await SaveLanguagesAsync(); - await SaveResourcesAsync(); - await SaveTextsAsync(); - - await PreCacheDynamicLocalizationsAsync(); - } - catch (Exception ex) - { - Logger.LogInformation("Filed to save static localizations."); - Logger.LogWarning(ex.ToString()); - try - { - await unitOfWork.RollbackAsync(); - } - catch - { - Logger.LogInformation("Filed to rollback saving static localizations."); - } - throw; - } - - await unitOfWork.CompleteAsync(); - Logger.LogInformation("Completed to save static localizations."); - } + var languageCacheItems = new Dictionary(); + var resourceCacheItems = new Dictionary(); + var textCacheItems = new Dictionary(); - private async Task PreCacheDynamicLocalizationsAsync() - { - if (LocalizationLanguageStoreCache is LocalizationLanguageStoreCache languageStoreCache) - { - await languageStoreCache.UpdateCache(); - } - - foreach (var resource in LocalizationOptions.Resources) + var localizationResources = LocalizationOptions.Resources.Values.OfType().ToArray(); + foreach (var language in LocalizationOptions.Languages) { - if (ExternalLocalizationStoreCache is ExternalLocalizationStoreCache localizationStoreCache) - { - await localizationStoreCache.UpdateCache(resource.Key); - } - - foreach (var language in LocalizationOptions.Languages) + if (!languageCacheItems.Any(x => x.Value.CultureName == language.CultureName)) { - if (ExternalLocalizationTextStoreCache is ExternalLocalizationTextStoreCache externalLocalizationTextStoreCache) - { - await externalLocalizationTextStoreCache.UpdateCache(resource.Value, language.CultureName); - } - if (LocalizationTextStoreCache is LocalizationTextStoreCache localizationTextStoreCache) - { - await localizationTextStoreCache.UpdateCache(resource.Value, language.CultureName); - } + languageCacheItems.Add( + Guid.NewGuid().ToString(), + new DynamicLanguageInitializerCacheItem( + language.CultureName, + language.UiCultureName, + language.DisplayName, + language.TwoLetterISOLanguageName)); } - } - } - private async Task SaveLanguagesAsync() - { - var languageHashKey = GetApplicationLanguageHashKey(); - var languageHashCache = await Cache.GetStringAsync(languageHashKey, CancellationTokenProvider.Token); - var languageHash = CalculateLanguagesHash(LocalizationOptions.Languages); - if (languageHashCache != languageHash) - { - var newLanguages = new List(); - foreach (var language in LocalizationOptions.Languages) + foreach (var resource in localizationResources) { - if (await LanguageRepository.FindByCultureNameAsync(language.CultureName, cancellationToken: CancellationTokenProvider.Token) == null) + if (!resourceCacheItems.Any(x => x.Value.ResourceName == resource.ResourceName)) { - newLanguages.Add( - new Language( - GuidGenerator.Create(), - language.CultureName, - language.UiCultureName, - language.DisplayName, - language.TwoLetterISOLanguageName)); + resourceCacheItems.Add( + Guid.NewGuid().ToString(), + new DynamicResourceInitializerCacheItem( + resource.ResourceName, + resource.DefaultCultureName)); } - } - if (newLanguages.Any()) - { - Logger.LogInformation("Saved {0} new languages.", newLanguages.Count); - - await LanguageRepository.InsertManyAsync(newLanguages, cancellationToken: CancellationTokenProvider.Token); - } - - await Cache.SetStringAsync(languageHashKey, languageHash, CancellationTokenProvider.Token); - } - } - - private async Task SaveResourcesAsync() - { - var resourceHashKey = GetApplicationResourceHashKey(); - var resourceHashCache = await Cache.GetStringAsync(resourceHashKey, CancellationTokenProvider.Token); - var resourceHash = CalculateResourceHash(LocalizationOptions.Resources.Select(x => x.Value).ToList()); - if (resourceHashCache != resourceHash) - { - var newResources = new List(); - foreach (var resource in LocalizationOptions.Resources) - { - if (await ResourceRepository.FindByNameAsync(resource.Key, cancellationToken: CancellationTokenProvider.Token) == null) - { - newResources.Add( - new Resource( - GuidGenerator.Create(), - resource.Value.ResourceName, - resource.Value.ResourceName, - resource.Value.ResourceName, - resource.Value.DefaultCultureName)); - } - } - if (newResources.Any()) - { - Logger.LogInformation("Saved {0} new resources.", newResources.Count); - - await ResourceRepository.InsertManyAsync(newResources, cancellationToken: CancellationTokenProvider.Token); - } - - await Cache.SetStringAsync(resourceHashKey, resourceHash, CancellationTokenProvider.Token); - } - } - - private async Task SaveTextsAsync() - { - var createTexts = new List(); - var updateTexts = new List(); - - var localizationResources = LocalizationOptions.Resources.Values.OfType().ToArray(); - - var languageResourceTexts = new Dictionary>>(); - - foreach (var language in LocalizationOptions.Languages) - { - var resourceTexts = new Dictionary>(); - foreach (var resource in localizationResources) - { using (CultureHelper.Use(language.CultureName, language.UiCultureName)) { var stringLocalizer = StringLocalizerFactory.Create(resource.ResourceType); var localizedStrings = stringLocalizer.GetAllStrings(false, false, false); - var textHashKey = GetApplicationTextHashKey(resource.ResourceName, language.CultureName); - var textHashCache = await Cache.GetStringAsync(textHashKey, CancellationTokenProvider.Token); - var textHash = CalculateTextsHash(localizedStrings); - if (textHashCache == textHash) + if (!localizedStrings.Any()) { continue; } - - var savedLocalizedStrings = await TextRepository.GetListAsync( - resource.ResourceName, - language.CultureName); + var localizedStringsDic = new Dictionary(); foreach (var localizedString in localizedStrings) { - var findLocalizedString = savedLocalizedStrings.FirstOrDefault(x => x.Key == localizedString.Name); - if (findLocalizedString == null) - { - createTexts.Add( - new Text( - resource.ResourceName, - language.CultureName, - localizedString.Name, - localizedString.Value)); - continue; - } - - if (!string.Equals(findLocalizedString.Value, localizedString.Value, StringComparison.InvariantCultureIgnoreCase)) - { - findLocalizedString.SetValue(localizedString.Value); - updateTexts.Add(findLocalizedString); - } + localizedStringsDic.TryAdd(localizedString.Name, localizedString.Value); } - await Cache.SetStringAsync(textHashKey, textHash, CancellationTokenProvider.Token); + textCacheItems.Add( + Guid.NewGuid().ToString(), + new DynamicTextInitializerCacheItem( + resource.ResourceName, + language.CultureName, + localizedStringsDic)); } } - - languageResourceTexts[language.CultureName] = resourceTexts; } - if (createTexts.Any()) - { - Logger.LogInformation("Saved {0} new texts.", createTexts.Count); - - await TextRepository.InsertManyAsync(createTexts, cancellationToken: CancellationTokenProvider.Token); - } - if (updateTexts.Any()) - { - Logger.LogInformation("Update {0} changed texts.", updateTexts.Count); - - await TextRepository.UpdateManyAsync(updateTexts, cancellationToken: CancellationTokenProvider.Token); - } - } - - private string GetApplicationDistributedLockKey() - { - return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationsUpdateLock"; - } - - private string GetApplicationResourceHashKey() - { - return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationResourcesHash"; - } - - private string GetApplicationLanguageHashKey() - { - return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationLanguagesHash"; - } - private string GetApplicationTextHashKey(string resourceName, string cultureName) - { - return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_{resourceName}_{cultureName}_AbpLocalizationTextsHash"; - } - - private static string CalculateResourceHash(List localizationResources) - { - var stringBuilder = new StringBuilder(); - - stringBuilder.Append("LocalizationResources:"); - stringBuilder.AppendLine(JsonSerializer.Serialize(localizationResources)); - - return stringBuilder - .ToString() - .ToMd5(); - } - - private static string CalculateLanguagesHash(List languageInfos) - { - var stringBuilder = new StringBuilder(); - - stringBuilder.Append("LocalizationLanguages:"); - stringBuilder.AppendLine(JsonSerializer.Serialize(languageInfos)); - - return stringBuilder - .ToString() - .ToMd5(); - } - - private static string CalculateTextsHash(IEnumerable localizers) - { - var stringBuilder = new StringBuilder(); - - stringBuilder.Append("LocalizationTexts:"); - stringBuilder.AppendLine(JsonSerializer.Serialize( - localizers.Select(x => new NameValue(x.Name, x.Value)).ToList())); + await DynamicLanguageCache.SetManyAsync(languageCacheItems); + await DynamicResourceCache.SetManyAsync(resourceCacheItems); + await DynamicTextCache.SetManyAsync(textCacheItems); - return stringBuilder - .ToString() - .ToMd5(); + await DistributedEventBus.PublishAsync( + new DynamicLanguageInitializerEto(languageCacheItems.Keys.ToArray())); + await DistributedEventBus.PublishAsync( + new DynamicResourceInitializerEto(resourceCacheItems.Keys.ToArray())); + await DistributedEventBus.PublishAsync( + new DynamicTextInitializerEto(textCacheItems.Keys.ToArray())); } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs index 269e308fe..e34aeddcc 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -40,4 +41,10 @@ public class EfCoreResourceRepository : EfCoreRepository x.Name.Equals(name)) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } + + public async virtual Task> GetActivedListAsync(CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()).Where(x => x.Enable) + .ToListAsync(GetCancellationToken(cancellationToken)); + } }