Browse Source

Avoid multiple enumeration in MultiLingualObjectManager

pull/25846/head
maliming 2 months ago
parent
commit
ff3de65b29
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 27
      framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs
  2. 90
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

27
framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs

@ -28,12 +28,13 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
{ {
culture ??= CultureInfo.CurrentUICulture.Name; culture ??= CultureInfo.CurrentUICulture.Name;
if (translations == null || !translations.Any()) var translationList = translations?.ToList();
if (translationList == null || translationList.Count == 0)
{ {
return null; return null;
} }
var translation = translations.FirstOrDefault(pt => pt.Language == culture); var translation = translationList.FirstOrDefault(pt => pt.Language == culture);
if (translation != null) if (translation != null)
{ {
return translation; return translation;
@ -43,7 +44,7 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
{ {
translation = GetTranslationBasedOnCulturalRecursive( translation = GetTranslationBasedOnCulturalRecursive(
CultureInfo.CurrentUICulture.Parent, CultureInfo.CurrentUICulture.Parent,
translations, translationList,
0 0
); );
@ -55,13 +56,13 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
translation = translations.FirstOrDefault(pt => pt.Language == defaultLanguage); translation = translationList.FirstOrDefault(pt => pt.Language == defaultLanguage);
if (translation != null) if (translation != null)
{ {
return translation; return translation;
} }
translation = translations.FirstOrDefault(); translation = translationList.FirstOrDefault();
return translation; return translation;
} }
@ -96,16 +97,17 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
{ {
culture ??= CultureInfo.CurrentUICulture.Name; culture ??= CultureInfo.CurrentUICulture.Name;
if (translationsCombined == null || !translationsCombined.Any()) var translationsCombinedList = translationsCombined?.Select(translations => translations.ToList()).ToList();
if (translationsCombinedList == null || translationsCombinedList.Count == 0)
{ {
return new(); return new();
} }
var someHaveNoTranslations = false; var someHaveNoTranslations = false;
var res = new List<TTranslation?>(); var res = new List<TTranslation?>();
foreach (var translations in translationsCombined) foreach (var translations in translationsCombinedList)
{ {
if (!translations.Any()) if (translations.Count == 0)
{ {
//if the src has no translations, don't try to find a translation //if the src has no translations, don't try to find a translation
res.Add(null); res.Add(null);
@ -150,10 +152,10 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
var index = 0; var index = 0;
foreach (var translations in translationsCombined) foreach (var translations in translationsCombinedList)
{ {
//if the src has no translations, don't try to find a translation //if the src has no translations, don't try to find a translation
if (translations.Any() && res[index] == null) if (translations.Count > 0 && res[index] == null)
{ {
res[index] = translations.FirstOrDefault(pt => pt.Language == defaultLanguage) ?? res[index] = translations.FirstOrDefault(pt => pt.Language == defaultLanguage) ??
translations.FirstOrDefault(); translations.FirstOrDefault();
@ -168,10 +170,11 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
where TMultiLingual : IMultiLingualObject<TTranslation> where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation where TTranslation : class, IObjectTranslation
{ {
var resInitial = await GetBulkTranslationsAsync(multiLinguals.Select(x => x.Translations), culture, fallbackToParentCultures); var multiLingualList = multiLinguals.ToList();
var resInitial = await GetBulkTranslationsAsync(multiLingualList.Select(x => x.Translations), culture, fallbackToParentCultures);
var index = 0; var index = 0;
var res = new List<(TMultiLingual entity, TTranslation? translation)>(); var res = new List<(TMultiLingual entity, TTranslation? translation)>();
foreach (var item in multiLinguals) foreach (var item in multiLingualList)
{ {
var t = resInitial[index++]; var t = resInitial[index++];
res.Add((item, t)); res.Add((item, t));

90
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Frozen; using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -188,6 +189,68 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
} }
} }
[Fact]
public async Task GetTranslationAsync_Should_Enumerate_Translations_Once()
{
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync(
new OneTimeEnumerable<MultiLingualBookTranslation>(_book.Translations));
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task GetBulkTranslationsAsync_Should_Enumerate_Entities_Once()
{
using (CultureHelper.Use("fr-FR"))
{
var books = new List<MultiLingualBook>
{
//resolved by the fallback pass
GetTestBook("ar", "en"),
//has no translations
GetTestBook(),
//resolved by the first-translation fallback
GetTestBook("ar")
};
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(
new OneTimeEnumerable<MultiLingualBook>(books));
translations.Count.ShouldBe(3);
translations[0].entity.ShouldBe(books[0]);
translations[0].translation!.Name.ShouldBe(_testTranslations["en"]);
translations[1].entity.ShouldBe(books[1]);
translations[1].translation.ShouldBeNull();
translations[2].entity.ShouldBe(books[2]);
translations[2].translation!.Name.ShouldBe(_testTranslations["ar"]);
}
}
[Fact]
public async Task GetBulkTranslationsAsync_Should_Enumerate_Translation_Sequences_Once()
{
using (CultureHelper.Use("fr-FR"))
{
var translationsCombined = new OneTimeEnumerable<IEnumerable<MultiLingualBookTranslation>>(
_books
.Select(x => (IEnumerable<MultiLingualBookTranslation>)new OneTimeEnumerable<MultiLingualBookTranslation>(x.Translations))
.ToList());
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(translationsCombined);
translations.Count.ShouldBe(_books.Count);
translations[0].ShouldBeNull();
translations[1]!.Name.ShouldBe(_testTranslations["en"]);
translations[2]!.Name.ShouldBe(_testTranslations["ar"]);
translations[3]!.Name.ShouldBe(_testTranslations["en"]);
translations[4]!.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact] [Fact]
public async Task TestBulkMapping() public async Task TestBulkMapping()
{ {
@ -210,4 +273,31 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
} }
} }
} }
private class OneTimeEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> _source;
private bool _enumerated;
public OneTimeEnumerable(IEnumerable<T> source)
{
_source = source;
}
public IEnumerator<T> GetEnumerator()
{
if (_enumerated)
{
throw new InvalidOperationException("The sequence was enumerated more than once");
}
_enumerated = true;
return _source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
} }

Loading…
Cancel
Save