question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

MultiTenantLocalizationDictionary fallback problem

See original GitHub issue

My 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:open
  • Created 5 years ago
  • Comments:9 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
staviloglucommented, Nov 4, 2020

@EslamElmadny I think you should seperate querying db and getting localization values, one with tenant filter and one without.

1reaction
staviloglucommented, Feb 22, 2019

@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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Developers - MultiTenantLocalizationDictionary fallback problem -
My application starts with xml based localization files as default, if any tenant changes a localized text than it is written to the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found