Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs

398 lines
15 KiB
C#
Raw Normal View History

using System;
2017-12-07 16:45:25 +01:00
using System.Collections.Generic;
using System.Linq;
2020-09-17 09:42:55 +02:00
using Microsoft.Extensions.Logging;
2017-12-07 16:45:25 +01:00
using NPoco;
Merge remote-tracking branch 'origin/v8/8.16' into v9/feature/merge_v8_11082021 # Conflicts: # .github/CONTRIBUTING.md # build/NuSpecs/UmbracoCms.Core.nuspec # build/NuSpecs/UmbracoCms.Web.nuspec # build/NuSpecs/UmbracoCms.nuspec # src/SolutionInfo.cs # src/Umbraco.Core/Cache/AppCaches.cs # src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs # src/Umbraco.Core/Cache/DeepCloneAppCache.cs # src/Umbraco.Core/Cache/WebCachingAppCache.cs # src/Umbraco.Core/CompositionExtensions.cs # src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs # src/Umbraco.Core/Models/PropertyGroupCollection.cs # src/Umbraco.Core/Models/PropertyTypeCollection.cs # src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs # src/Umbraco.Core/ReadLock.cs # src/Umbraco.Core/Routing/SiteDomainMapper.cs # src/Umbraco.Core/UpgradeableReadLock.cs # src/Umbraco.Core/WriteLock.cs # src/Umbraco.Examine/ExamineExtensions.cs # src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs # src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeDto.cs # src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs # src/Umbraco.Infrastructure/Services/IdKeyMap.cs # src/Umbraco.Infrastructure/Services/Implement/ContentService.cs # src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs # src/Umbraco.Tests/App.config # src/Umbraco.Web.BackOffice/Controllers/EntityController.cs # src/Umbraco.Web.UI.Client/package.json # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco.Web.UI.csproj # src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/CacheHelperExtensions.cs # src/Umbraco.Web/Editors/RelationTypeController.cs # src/Umbraco.Web/Logging/WebProfilerProvider.cs # src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs # src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs # src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs # src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs # src/Umbraco.Web/Security/BackOfficeUserManager.cs # src/Umbraco.Web/Umbraco.Web.csproj
2021-08-11 19:11:35 +02:00
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
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;
using Umbraco.Extensions;
2017-12-07 16:45:25 +01:00
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
2017-12-07 16:45:25 +01:00
{
/// <summary>
/// Represents a repository for doing CRUD operations for <see cref="DictionaryItem"/>
/// </summary>
2020-12-22 10:30:16 +11:00
internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>, IDictionaryRepository
2017-12-07 16:45:25 +01:00
{
2020-09-17 09:42:55 +02:00
private readonly ILoggerFactory _loggerFactory;
public DictionaryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<DictionaryRepository> logger, ILoggerFactory loggerFactory)
2017-12-14 17:04:44 +01:00
: base(scopeAccessor, cache, logger)
2020-09-17 09:42:55 +02:00
{
_loggerFactory = loggerFactory;
}
2017-12-07 16:45:25 +01:00
protected override IRepositoryCachePolicy<IDictionaryItem, int> CreateCachePolicy()
2017-12-07 16:45:25 +01:00
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, int>(GlobalIsolatedCache, ScopeAccessor, options);
2017-12-07 16:45:25 +01:00
}
#region Overrides of RepositoryBase<int,DictionaryItem>
protected override IDictionaryItem? PerformGet(int id)
2017-12-07 16:45:25 +01:00
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { id = id })
.OrderBy<DictionaryDto>(x => x.UniqueId);
var dto = Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.FirstOrDefault();
if (dto == null)
return null;
var entity = ConvertFromDto(dto);
// reset dirty initial properties (U4-1946)
((EntityBase)entity).ResetDirtyProperties(false);
2017-12-07 16:45:25 +01:00
return entity;
}
protected override IEnumerable<IDictionaryItem> PerformGetAll(params int[]? ids)
2017-12-07 16:45:25 +01:00
{
var sql = GetBaseQuery(false).Where<DictionaryDto>(x => x.PrimaryKey > 0);
if (ids?.Any() ?? false)
2017-12-07 16:45:25 +01:00
{
sql.WhereIn<DictionaryDto>(x => x.PrimaryKey, ids);
2017-12-07 16:45:25 +01:00
}
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
}
protected override IEnumerable<IDictionaryItem> PerformGetByQuery(IQuery<IDictionaryItem> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IDictionaryItem>(sqlClause, query);
var sql = translator.Translate();
sql.OrderBy<DictionaryDto>(x => x.UniqueId);
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
}
#endregion
2020-12-22 10:30:16 +11:00
#region Overrides of EntityRepositoryBase<int,DictionaryItem>
2017-12-07 16:45:25 +01:00
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
if (isCount)
{
sql.SelectCount()
.From<DictionaryDto>();
}
else
{
sql.SelectAll()
.From<DictionaryDto>()
.LeftJoin<LanguageTextDto>()
.On<DictionaryDto, LanguageTextDto>(left => left.UniqueId, right => right.UniqueId);
}
return sql;
}
protected override string GetBaseWhereClause()
{
return $"{Constants.DatabaseSchema.Tables.DictionaryEntry}.pk = @id";
2017-12-07 16:45:25 +01:00
}
protected override IEnumerable<string> GetDeleteClauses()
{
return new List<string>();
}
#endregion
#region Unit of Work Implementation
protected override void PersistNewItem(IDictionaryItem entity)
{
var dictionaryItem = ((DictionaryItem) entity);
dictionaryItem.AddingEntity();
foreach (var translation in dictionaryItem.Translations)
translation.Value = translation.Value.ToValidXmlString();
2020-09-17 09:42:55 +02:00
2018-06-29 11:05:23 +01:00
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
2017-12-07 16:45:25 +01:00
var id = Convert.ToInt32(Database.Insert(dto));
dictionaryItem.Id = id;
foreach (var translation in dictionaryItem.Translations)
{
var textDto = DictionaryTranslationFactory.BuildDto(translation, dictionaryItem.Key);
2017-12-07 16:45:25 +01:00
translation.Id = Convert.ToInt32(Database.Insert(textDto));
translation.Key = dictionaryItem.Key;
}
dictionaryItem.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDictionaryItem entity)
{
entity.UpdatingEntity();
2017-12-07 16:45:25 +01:00
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
2020-09-17 09:42:55 +02:00
2018-06-29 11:05:23 +01:00
var dto = DictionaryItemFactory.BuildDto(entity);
2017-12-07 16:45:25 +01:00
Database.Update(dto);
foreach (var translation in entity.Translations)
{
var textDto = DictionaryTranslationFactory.BuildDto(translation, entity.Key);
2017-12-07 16:45:25 +01:00
if (translation.HasIdentity)
{
Database.Update(textDto);
}
else
{
translation.Id = Convert.ToInt32(Database.Insert(textDto));
translation.Key = entity.Key;
}
}
entity.ResetDirtyProperties();
//Clear the cache entries that exist by uniqueid/item key
Implements Public Access in netcore (#10137) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 15:11:45 +10:00
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
2017-12-07 16:45:25 +01:00
}
protected override void PersistDeletedItem(IDictionaryItem entity)
{
RecursiveDelete(entity.Key);
Database.Delete<LanguageTextDto>("WHERE UniqueId = @Id", new { Id = entity.Key });
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
Implements Public Access in netcore (#10137) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 15:11:45 +10:00
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
2017-12-07 16:45:25 +01:00
entity.DeleteDate = DateTime.Now;
2017-12-07 16:45:25 +01:00
}
private void RecursiveDelete(Guid parentId)
{
var list = Database.Fetch<DictionaryDto>("WHERE parent = @ParentId", new { ParentId = parentId });
foreach (var dto in list)
{
RecursiveDelete(dto.UniqueId);
Database.Delete<LanguageTextDto>("WHERE UniqueId = @Id", new { Id = dto.UniqueId });
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
//Clear the cache entries that exist by uniqueid/item key
Implements Public Access in netcore (#10137) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 15:11:45 +10:00
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
2017-12-07 16:45:25 +01:00
}
}
#endregion
protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
{
2018-06-29 11:05:23 +01:00
var entity = DictionaryItemFactory.BuildEntity(dto);
2017-12-07 16:45:25 +01:00
entity.Translations = dto.LanguageTextDtos.EmptyNull()
.Where(x => x.LanguageId > 0)
.Select(x => DictionaryTranslationFactory.BuildEntity(x, dto.UniqueId))
2017-12-07 16:45:25 +01:00
.ToList();
return entity;
}
public IDictionaryItem? Get(Guid uniqueId)
2017-12-07 16:45:25 +01:00
{
2020-09-17 09:42:55 +02:00
var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches, _loggerFactory.CreateLogger<DictionaryByUniqueIdRepository>());
2017-12-07 16:45:25 +01:00
return uniqueIdRepo.Get(uniqueId);
}
public IDictionaryItem? Get(string key)
2017-12-07 16:45:25 +01:00
{
2020-09-17 09:42:55 +02:00
var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches, _loggerFactory.CreateLogger<DictionaryByKeyRepository>());
2017-12-07 16:45:25 +01:00
return keyRepo.Get(key);
}
private IEnumerable<IDictionaryItem>? GetRootDictionaryItems()
2017-12-07 16:45:25 +01:00
{
var query = Query<IDictionaryItem>().Where(x => x.ParentId == null);
return Get(query);
}
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
{
var columns = new[] { "key", "id" }.Select(x => (object) SqlSyntax.GetQuotedColumnName(x)).ToArray();
var sql = Sql().Select(columns).From<DictionaryDto>();
return Database.Fetch<DictionaryItemKeyIdDto>(sql).ToDictionary(x => x.Key, x => x.Id);
}
private class DictionaryItemKeyIdDto
{
public string Key { get; set; } = null!;
2017-12-07 16:45:25 +01:00
public Guid Id { get; set; }
}
public IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId)
{
//This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive
// lookup to get descendants. Currently this is the most efficient way to do it
Func<Guid[], IEnumerable<IEnumerable<IDictionaryItem>>> getItemsFromParents = guids =>
{
return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
2021-10-13 13:46:51 +02:00
.Select(group =>
2017-12-07 16:45:25 +01:00
{
var sqlClause = GetBaseQuery(false)
.Where<DictionaryDto>(x => x.Parent != null)
2021-10-13 13:46:51 +02:00
.WhereIn<DictionaryDto>(x => x.Parent, group);
2017-12-07 16:45:25 +01:00
var translator = new SqlTranslator<IDictionaryItem>(sqlClause, Query<IDictionaryItem>());
var sql = translator.Translate();
sql.OrderBy<DictionaryDto>(x => x.UniqueId);
return Database
.FetchOneToMany<DictionaryDto>(x=> x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
});
};
var childItems = parentId.HasValue == false
? new[] { GetRootDictionaryItems()! }
2017-12-07 16:45:25 +01:00
: getItemsFromParents(new[] { parentId.Value });
return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items);
}
private class DictionaryByUniqueIdRepository : SimpleGetRepository<Guid, IDictionaryItem, DictionaryDto>
{
private readonly DictionaryRepository _dictionaryRepository;
2020-09-17 09:42:55 +02:00
public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger<DictionaryByUniqueIdRepository> logger)
2017-12-14 17:04:44 +01:00
: base(scopeAccessor, cache, logger)
2017-12-07 16:45:25 +01:00
{
_dictionaryRepository = dictionaryRepository;
}
protected override IEnumerable<DictionaryDto> PerformFetch(Sql sql)
{
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql);
}
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
return _dictionaryRepository.GetBaseQuery(isCount);
}
protected override string GetBaseWhereClause()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " = @id";
}
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
return _dictionaryRepository.ConvertFromDto(dto);
}
protected override object GetBaseWhereClauseArguments(Guid id)
{
return new { id = id };
}
protected override string GetWhereInClauseForGetAll()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)";
}
protected override IRepositoryCachePolicy<IDictionaryItem, Guid> CreateCachePolicy()
2017-12-07 16:45:25 +01:00
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, Guid>(GlobalIsolatedCache, ScopeAccessor, options);
2017-12-07 16:45:25 +01:00
}
}
private class DictionaryByKeyRepository : SimpleGetRepository<string, IDictionaryItem, DictionaryDto>
{
private readonly DictionaryRepository _dictionaryRepository;
2020-09-17 09:42:55 +02:00
public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger<DictionaryByKeyRepository> logger)
2017-12-14 17:04:44 +01:00
: base(scopeAccessor, cache, logger)
2017-12-07 16:45:25 +01:00
{
_dictionaryRepository = dictionaryRepository;
}
protected override IEnumerable<DictionaryDto> PerformFetch(Sql sql)
{
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql);
}
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
return _dictionaryRepository.GetBaseQuery(isCount);
}
protected override string GetBaseWhereClause()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " = @id";
}
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
return _dictionaryRepository.ConvertFromDto(dto);
}
protected override object GetBaseWhereClauseArguments(string? id)
2017-12-07 16:45:25 +01:00
{
return new { id = id };
}
protected override string GetWhereInClauseForGetAll()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)";
}
protected override IRepositoryCachePolicy<IDictionaryItem, string> CreateCachePolicy()
2017-12-07 16:45:25 +01:00
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, string>(GlobalIsolatedCache, ScopeAccessor, options);
2017-12-07 16:45:25 +01:00
}
}
}
}