2020-09-17 09:42:55 +02:00
|
|
|
using Microsoft.Extensions.Logging;
|
2017-12-07 16:45:25 +01:00
|
|
|
using NPoco;
|
2021-08-11 19:11:35 +02:00
|
|
|
using Umbraco.Cms.Core;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core.Cache;
|
|
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
using Umbraco.Cms.Core.Persistence.Querying;
|
|
|
|
|
using Umbraco.Cms.Core.Persistence.Repositories;
|
2021-02-12 13:36:50 +01:00
|
|
|
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
|
|
|
|
using Umbraco.Cms.Infrastructure.Persistence.Factories;
|
|
|
|
|
using Umbraco.Cms.Infrastructure.Persistence.Querying;
|
2022-01-13 17:44:11 +00:00
|
|
|
using Umbraco.Cms.Infrastructure.Scoping;
|
2021-02-09 11:26:22 +01:00
|
|
|
using Umbraco.Extensions;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents a repository for doing CRUD operations for <see cref="Language" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class LanguageRepository : EntityRepositoryBase<int, ILanguage>, ILanguageRepository
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
private readonly Dictionary<string, int> _codeIdMap = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
private readonly Dictionary<int, string> _idCodeMap = new();
|
|
|
|
|
|
|
|
|
|
public LanguageRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<LanguageRepository> logger)
|
|
|
|
|
: base(scopeAccessor, cache, logger)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2018-04-11 15:31:21 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private FullDataSetRepositoryCachePolicy<ILanguage, int>? TypedCachePolicy =>
|
|
|
|
|
CachePolicy as FullDataSetRepositoryCachePolicy<ILanguage, int>;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public ILanguage? GetByIsoCode(string isoCode)
|
|
|
|
|
{
|
2023-02-01 09:37:37 +01:00
|
|
|
EnsureCacheIsPopulated();
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var id = GetIdByIsoCode(isoCode, false);
|
|
|
|
|
return id.HasValue ? Get(id.Value) : null;
|
|
|
|
|
}
|
2018-04-12 22:53:04 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// fast way of getting an id for an isoCode - avoiding cloning
|
|
|
|
|
// _codeIdMap is rebuilt whenever PerformGetAll runs
|
|
|
|
|
public int? GetIdByIsoCode(string? isoCode, bool throwOnNotFound = true)
|
|
|
|
|
{
|
|
|
|
|
if (isoCode == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
EnsureCacheIsPopulated();
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
lock (_codeIdMap)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (_codeIdMap.TryGetValue(isoCode, out var id))
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
return id;
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (throwOnNotFound)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
2018-06-12 11:11:16 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// fast way of getting an isoCode for an id - avoiding cloning
|
|
|
|
|
// _idCodeMap is rebuilt whenever PerformGetAll runs
|
|
|
|
|
public string? GetIsoCodeById(int? id, bool throwOnNotFound = true)
|
|
|
|
|
{
|
|
|
|
|
if (id == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2018-04-11 15:31:21 +02:00
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
EnsureCacheIsPopulated();
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// yes, we want to lock _codeIdMap
|
|
|
|
|
lock (_codeIdMap)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (_idCodeMap.TryGetValue(id.Value, out var isoCode))
|
|
|
|
|
{
|
|
|
|
|
return isoCode;
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (throwOnNotFound)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"Id {id} does not correspond to an existing language.", nameof(id));
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public string GetDefaultIsoCode() => GetDefault().IsoCode;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public int? GetDefaultId() => GetDefault().Id;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override IRepositoryCachePolicy<ILanguage, int> CreateCachePolicy() =>
|
|
|
|
|
new FullDataSetRepositoryCachePolicy<ILanguage, int>(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false);
|
2021-08-06 11:32:16 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected ILanguage ConvertFromDto(LanguageDto dto)
|
2023-02-01 09:37:37 +01:00
|
|
|
{
|
|
|
|
|
// yes, we want to lock _codeIdMap
|
|
|
|
|
lock (_codeIdMap)
|
|
|
|
|
{
|
|
|
|
|
string? fallbackIsoCode = null;
|
|
|
|
|
if (dto.FallbackLanguageId.HasValue && _idCodeMap.TryGetValue(dto.FallbackLanguageId.Value, out fallbackIsoCode) == false)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"The ISO code map did not contain ISO code for fallback language ID: {dto.FallbackLanguageId}. Please reload the caches.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LanguageFactory.BuildEntity(dto, fallbackIsoCode);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// do NOT leak that language, it's not deep-cloned!
|
|
|
|
|
private ILanguage GetDefault()
|
|
|
|
|
{
|
|
|
|
|
// get all cached
|
|
|
|
|
var languages =
|
|
|
|
|
(TypedCachePolicy
|
|
|
|
|
?.GetAllCached(
|
|
|
|
|
PerformGetAll) // Try to get all cached non-cloned if using the correct cache policy (not the case in unit tests)
|
|
|
|
|
?? CachePolicy.GetAll(Array.Empty<int>(), PerformGetAll)).ToList();
|
|
|
|
|
|
|
|
|
|
ILanguage? language = languages.FirstOrDefault(x => x.IsDefault);
|
|
|
|
|
if (language != null)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
return language;
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// this is an anomaly, the service/repo should ensure it cannot happen
|
|
|
|
|
Logger.LogWarning(
|
|
|
|
|
"There is no default language. Fix this anomaly by editing the language table in database and setting one language as the default language.");
|
|
|
|
|
|
|
|
|
|
// still, don't kill the site, and return "something"
|
|
|
|
|
ILanguage? first = null;
|
|
|
|
|
foreach (ILanguage l in languages)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (first == null || l.Id < first.Id)
|
|
|
|
|
{
|
|
|
|
|
first = l;
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return first!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Overrides of RepositoryBase<int,Language>
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override ILanguage? PerformGet(int id) => PerformGetAll(id).FirstOrDefault();
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override IEnumerable<ILanguage> PerformGetAll(params int[]? ids)
|
|
|
|
|
{
|
|
|
|
|
Sql<ISqlContext> sql = GetBaseQuery(false).Where<LanguageDto>(x => x.Id > 0);
|
|
|
|
|
if (ids?.Any() ?? false)
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
sql.WhereIn<LanguageDto>(x => x.Id, ids);
|
|
|
|
|
}
|
2018-03-30 01:20:40 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// this needs to be sorted since that is the way legacy worked - default language is the first one!!
|
|
|
|
|
// even though legacy didn't sort, it should be by id
|
|
|
|
|
sql.OrderBy<LanguageDto>(x => x.Id);
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// get languages
|
2023-02-01 09:37:37 +01:00
|
|
|
List<LanguageDto>? languageDtos = Database.Fetch<LanguageDto>(sql) ?? new List<LanguageDto>();
|
2022-06-02 08:18:31 +02:00
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
// initialize the code-id map if we've reloaded the entire set of languages
|
|
|
|
|
if (ids?.Any() == false)
|
2022-06-02 08:18:31 +02:00
|
|
|
{
|
2023-02-01 09:37:37 +01:00
|
|
|
lock (_codeIdMap)
|
2018-03-22 23:15:52 +11:00
|
|
|
{
|
2023-02-01 09:37:37 +01:00
|
|
|
_codeIdMap.Clear();
|
|
|
|
|
_idCodeMap.Clear();
|
|
|
|
|
foreach (LanguageDto languageDto in languageDtos)
|
|
|
|
|
{
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(languageDto.IsoCode, nameof(LanguageDto.IsoCode));
|
|
|
|
|
_codeIdMap[languageDto.IsoCode] = languageDto.Id;
|
|
|
|
|
_idCodeMap[languageDto.Id] = languageDto.IsoCode;
|
|
|
|
|
}
|
2018-03-22 23:15:52 +11:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2018-09-13 14:59:45 +02:00
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
var languages = languageDtos.Select(ConvertFromDto).OrderBy(x => x.Id).ToList();
|
2022-06-02 08:18:31 +02:00
|
|
|
return languages;
|
|
|
|
|
}
|
2018-09-13 14:59:45 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override IEnumerable<ILanguage> PerformGetByQuery(IQuery<ILanguage> query)
|
|
|
|
|
{
|
|
|
|
|
Sql<ISqlContext> sqlClause = GetBaseQuery(false);
|
|
|
|
|
var translator = new SqlTranslator<ILanguage>(sqlClause, query);
|
|
|
|
|
Sql<ISqlContext> sql = translator.Translate();
|
|
|
|
|
List<LanguageDto>? dtos = Database.Fetch<LanguageDto>(sql);
|
|
|
|
|
return dtos.Select(ConvertFromDto).ToList();
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#endregion
|
2018-03-30 01:20:40 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#region Overrides of EntityRepositoryBase<int,Language>
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
|
|
|
|
|
{
|
|
|
|
|
Sql<ISqlContext> sql = Sql();
|
2018-09-12 11:47:04 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
sql = isCount
|
|
|
|
|
? sql.SelectCount()
|
|
|
|
|
: sql.Select<LanguageDto>();
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
sql.From<LanguageDto>();
|
2019-10-10 20:45:16 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return sql;
|
|
|
|
|
}
|
2018-09-13 14:59:45 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.Language}.id = @id";
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override IEnumerable<string> GetDeleteClauses()
|
|
|
|
|
{
|
|
|
|
|
var list = new List<string>
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// NOTE: There is no constraint between the Language and cmsDictionary/cmsLanguageText tables (?)
|
|
|
|
|
// but we still need to remove them
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.DictionaryValue + " WHERE languageId = @id",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE languageId = @id",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE languageId = @id",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE languageId = @id",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE tagId IN (SELECT id FROM " +
|
|
|
|
|
Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id)",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id",
|
|
|
|
|
"DELETE FROM " + Constants.DatabaseSchema.Tables.Language + " WHERE id = @id",
|
|
|
|
|
};
|
|
|
|
|
return list;
|
|
|
|
|
}
|
2018-03-29 23:48:54 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#endregion
|
2018-03-29 23:48:54 +11:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
#region Unit of Work Implementation
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override void PersistNewItem(ILanguage entity)
|
|
|
|
|
{
|
|
|
|
|
// validate iso code and culture name
|
|
|
|
|
if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name.");
|
|
|
|
|
}
|
2018-09-13 14:59:45 +02:00
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
EnsureCacheIsPopulated();
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
entity.AddingEntity();
|
2018-07-21 08:24:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// deal with entity becoming the new default entity
|
|
|
|
|
if (entity.IsDefault)
|
|
|
|
|
{
|
|
|
|
|
// set all other entities to non-default
|
|
|
|
|
// safe (no race cond) because the service locks languages
|
|
|
|
|
Sql<ISqlContext> setAllDefaultToFalse = Sql()
|
|
|
|
|
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
|
|
|
|
Database.Execute(setAllDefaultToFalse);
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// fallback cycles are detected at service level
|
2017-12-07 16:45:25 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// insert
|
2023-02-01 09:37:37 +01:00
|
|
|
LanguageDto dto = LanguageFactory.BuildDto(entity, GetFallbackLanguageId(entity));
|
2022-06-02 08:18:31 +02:00
|
|
|
var id = Convert.ToInt32(Database.Insert(dto));
|
|
|
|
|
entity.Id = id;
|
|
|
|
|
entity.ResetDirtyProperties();
|
2023-02-01 09:37:37 +01:00
|
|
|
|
|
|
|
|
// yes, we want to lock _codeIdMap
|
|
|
|
|
lock (_codeIdMap)
|
|
|
|
|
{
|
|
|
|
|
_codeIdMap[entity.IsoCode] = entity.Id;
|
|
|
|
|
_idCodeMap[entity.Id] = entity.IsoCode;
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2018-07-05 16:00:53 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override void PersistUpdatedItem(ILanguage entity)
|
|
|
|
|
{
|
|
|
|
|
// validate iso code and culture name
|
|
|
|
|
if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace())
|
2017-12-07 16:45:25 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name.");
|
2018-04-11 15:31:21 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-01 09:37:37 +01:00
|
|
|
EnsureCacheIsPopulated();
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
entity.UpdatingEntity();
|
|
|
|
|
|
|
|
|
|
if (entity.IsDefault)
|
2018-04-11 15:31:21 +02:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// deal with entity becoming the new default entity
|
2018-04-21 09:57:28 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// set all other entities to non-default
|
|
|
|
|
// safe (no race cond) because the service locks languages
|
|
|
|
|
Sql<ISqlContext> setAllDefaultToFalse = Sql()
|
|
|
|
|
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
|
|
|
|
Database.Execute(setAllDefaultToFalse);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// deal with the entity not being default anymore
|
|
|
|
|
// which is illegal - another entity has to become default
|
|
|
|
|
Sql<ISqlContext> selectDefaultId = Sql()
|
|
|
|
|
.Select<LanguageDto>(x => x.Id)
|
|
|
|
|
.From<LanguageDto>()
|
|
|
|
|
.Where<LanguageDto>(x => x.IsDefault);
|
2018-10-03 19:03:22 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
|
|
|
|
if (entity.Id == defaultId)
|
2018-04-11 15:31:21 +02:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
|
2018-04-11 15:31:21 +02:00
|
|
|
}
|
2018-11-05 13:59:55 +11:00
|
|
|
}
|
2019-11-05 13:45:42 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
|
2018-04-11 15:31:21 +02:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// If the iso code is changing, ensure there's not another lang with the same code already assigned
|
|
|
|
|
Sql<ISqlContext> sameCode = Sql()
|
|
|
|
|
.SelectCount()
|
|
|
|
|
.From<LanguageDto>()
|
|
|
|
|
.Where<LanguageDto>(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
|
2018-10-03 19:03:22 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var countOfSameCode = Database.ExecuteScalar<int>(sameCode);
|
|
|
|
|
if (countOfSameCode > 0)
|
2018-04-11 15:31:21 +02:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
|
2018-04-11 15:31:21 +02:00
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
2018-04-26 16:03:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// fallback cycles are detected at service level
|
2018-04-26 16:03:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// update
|
2023-02-01 09:37:37 +01:00
|
|
|
LanguageDto dto = LanguageFactory.BuildDto(entity, GetFallbackLanguageId(entity));
|
2022-06-02 08:18:31 +02:00
|
|
|
Database.Update(dto);
|
|
|
|
|
entity.ResetDirtyProperties();
|
2023-02-01 09:37:37 +01:00
|
|
|
|
|
|
|
|
// yes, we want to lock _codeIdMap
|
|
|
|
|
lock (_codeIdMap)
|
|
|
|
|
{
|
|
|
|
|
_codeIdMap.RemoveAll(kvp => kvp.Value == entity.Id);
|
|
|
|
|
_codeIdMap[entity.IsoCode] = entity.Id;
|
|
|
|
|
_idCodeMap[entity.Id] = entity.IsoCode;
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2018-04-26 16:03:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
protected override void PersistDeletedItem(ILanguage entity)
|
|
|
|
|
{
|
|
|
|
|
// validate that the entity is not the default language.
|
|
|
|
|
// safe (no race cond) because the service locks languages
|
|
|
|
|
Sql<ISqlContext> selectDefaultId = Sql()
|
|
|
|
|
.Select<LanguageDto>(x => x.Id)
|
|
|
|
|
.From<LanguageDto>()
|
|
|
|
|
.Where<LanguageDto>(x => x.IsDefault);
|
|
|
|
|
|
|
|
|
|
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
|
|
|
|
if (entity.Id == defaultId)
|
2018-04-26 16:03:08 +02:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode}).");
|
|
|
|
|
}
|
2018-09-12 11:47:04 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// We need to remove any references to the language if it's being used as a fall-back from other ones
|
|
|
|
|
Sql<ISqlContext> clearFallbackLanguage = Sql()
|
|
|
|
|
.Update<LanguageDto>(u => u
|
|
|
|
|
.Set(x => x.FallbackLanguageId, null))
|
|
|
|
|
.Where<LanguageDto>(x => x.FallbackLanguageId == entity.Id);
|
2018-04-26 16:03:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
Database.Execute(clearFallbackLanguage);
|
2018-04-26 16:03:08 +02:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// delete
|
|
|
|
|
base.PersistDeletedItem(entity);
|
2023-02-01 09:37:37 +01:00
|
|
|
|
|
|
|
|
// yes, we want to lock _codeIdMap
|
|
|
|
|
lock (_codeIdMap)
|
|
|
|
|
{
|
|
|
|
|
_codeIdMap.RemoveAll(kvp => kvp.Value == entity.Id);
|
|
|
|
|
_idCodeMap.Remove(entity.Id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureCacheIsPopulated()
|
|
|
|
|
{
|
|
|
|
|
// ensure cache is populated, in a non-expensive way
|
|
|
|
|
if (TypedCachePolicy != null)
|
|
|
|
|
{
|
|
|
|
|
TypedCachePolicy.GetAllCached(PerformGetAll);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
PerformGetAll(); // We don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int? GetFallbackLanguageId(ILanguage entity)
|
|
|
|
|
{
|
|
|
|
|
int? fallbackLanguageId = null;
|
|
|
|
|
if (entity.FallbackIsoCode.IsNullOrWhiteSpace() == false &&
|
|
|
|
|
_codeIdMap.TryGetValue(entity.FallbackIsoCode, out var languageId))
|
|
|
|
|
{
|
|
|
|
|
fallbackLanguageId = languageId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fallbackLanguageId;
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
|
|
|
|
|
#endregion
|
2017-12-07 16:45:25 +01:00
|
|
|
}
|