MultiTenantLocalizationDictionary fallback problem
See original GitHub issueMy application starts with xml based localization files as default, if any tenant changes a localized text than it is written to the database with the tenantId. So my database has same keys with differenet tenantIds.
Most of the time, thanks to ABP, this works like a charm but sometime I see the eror message below in my logfile.
IMO that is because, somehow user is inactive, while session still exists but the cache is invalidated. MultiTenantLocalizationDictionary first tries the cache with tenantId from the session, cant’find the key than goes to the database with the tenantId (as I mentioned before if the tenant didn’t change any text, there is no text in database for that tenant) can’t find the key again.
Than decides to try without the tenantId again noluck in cache but when it goes to database without the tenantId there are duplicate keys with different tenantIds and this leads to an exception turning queryResult to dictionary.
If it had tried the _internalDictionary in the first place, which is the XmlLocalizationDictionary, it would find the key/value pair required.
MultiTenantLocalizationDictionary:
public LocalizedString GetOrNull(string name)
{
return GetOrNull(_session.TenantId, name);
}
public LocalizedString GetOrNull(int? tenantId, string name)
{
//Get cache
var cache = _cacheManager.GetMultiTenantLocalizationDictionaryCache();
//Get for current tenant
var dictionary = cache.Get(CalculateCacheKey(tenantId), () => GetAllValuesFromDatabase(tenantId));
var value = dictionary.GetOrDefault(name);
if (value != null)
{
return new LocalizedString(name, value, CultureInfo);
}
//Fall back to host
if (tenantId != null)
{
dictionary = cache.Get(CalculateCacheKey(null), () => GetAllValuesFromDatabase(null));
value = dictionary.GetOrDefault(name);
if (value != null)
{
return new LocalizedString(name, value, CultureInfo);
}
}
//Not found in database, fall back to internal dictionary
var internalLocalizedString = _internalDictionary.GetOrNull(name);
if (internalLocalizedString != null)
{
return internalLocalizedString;
}
//Not found at all
return null;
}
[UnitOfWork]
protected virtual Dictionary<string, string> GetAllValuesFromDatabase(int? tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
return _customLocalizationRepository
.GetAllList(l => l.Source == _sourceName && l.LanguageName == CultureInfo.Name)
.ToDictionary(l => l.Key, l => l.Value);
}
}
Abp.Zero.Common, Version=3.8.2.0
.Net Framework 4.6.1
Stack Trace:
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at Abp.Localization.MultiTenantLocalizationDictionary.GetAllValuesFromDatabase(Nullable`1 tenantId)
at Castle.Proxies.Invocations.MultiTenantLocalizationDictionary_GetAllValuesFromDatabase.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Abp.Domain.Uow.UnitOfWorkInterceptor.PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.MultiTenantLocalizationDictionaryProxy.GetAllValuesFromDatabase(Nullable`1 tenantId)
at Abp.Runtime.Caching.CacheExtensions.<>c__DisplayClass3_0`2.<Get>b__0(String k)
at Abp.Runtime.Caching.CacheBase.Get(String key, Func`2 factory)
at Abp.Runtime.Caching.CacheExtensions.Get[TKey,TValue](ICache cache, TKey key, Func`2 factory)
at Abp.Localization.MultiTenantLocalizationDictionary.GetOrNull(Nullable`1 tenantId, String name)
at Castle.Proxies.MultiTenantLocalizationDictionaryProxy.GetOrNull_callback(String name)
at Castle.Proxies.Invocations.ILocalizationDictionary_GetOrNull.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.MultiTenantLocalizationDictionaryProxy.GetOrNull(String name)
at Abp.Localization.Dictionaries.DictionaryBasedLocalizationSource.GetStringOrNull(String name, CultureInfo culture, Boolean tryDefaults)
at Abp.Localization.Dictionaries.DictionaryBasedLocalizationSource.GetString(String name, CultureInfo culture)
Issue Analytics
- State:
- Created 5 years ago
- Comments:9 (8 by maintainers)
Top GitHub Comments
@EslamElmadny I think you should seperate querying db and getting localization values, one with tenant filter and one without.
@ismcagdas @cangunaydin found out that; we were using
CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant, AbpDataFilters.MustHaveTenant)
which leads to checking database without the tenantfilter so the results returning from the database have same keys from different tenants. Added the filters back and localization is now working.IMO disabling those filters for the specific data I need shouldn’t effect localization functions. Localization should be handled seperately.