using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
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.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.Factories;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
///
/// Represents a repository for doing CRUD operations for
///
internal class DictionaryRepository : EntityRepositoryBase, IDictionaryRepository
{
private readonly ILoggerFactory _loggerFactory;
public DictionaryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ILoggerFactory loggerFactory)
: base(scopeAccessor, cache, logger)
{
_loggerFactory = loggerFactory;
}
protected override IRepositoryCachePolicy CreateCachePolicy()
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options);
}
#region Overrides of RepositoryBase
protected override IDictionaryItem PerformGet(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { id = id })
.OrderBy(x => x.UniqueId);
var dto = Database
.FetchOneToMany(x => x.LanguageTextDtos, sql)
.FirstOrDefault();
if (dto == null)
return null;
var entity = ConvertFromDto(dto);
// reset dirty initial properties (U4-1946)
((EntityBase)entity).ResetDirtyProperties(false);
return entity;
}
protected override IEnumerable PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(false).Where(x => x.PrimaryKey > 0);
if (ids.Any())
{
sql.WhereIn(x => x.PrimaryKey, ids);
}
return Database
.FetchOneToMany(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
}
protected override IEnumerable PerformGetByQuery(IQuery query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate();
sql.OrderBy(x => x.UniqueId);
return Database
.FetchOneToMany(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
}
#endregion
#region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
var sql = Sql();
if (isCount)
{
sql.SelectCount()
.From();
}
else
{
sql.SelectAll()
.From()
.LeftJoin()
.On(left => left.UniqueId, right => right.UniqueId);
}
return sql;
}
protected override string GetBaseWhereClause()
{
return $"{Constants.DatabaseSchema.Tables.DictionaryEntry}.pk = @id";
}
protected override IEnumerable GetDeleteClauses()
{
return new List();
}
#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();
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
var id = Convert.ToInt32(Database.Insert(dto));
dictionaryItem.Id = id;
foreach (var translation in dictionaryItem.Translations)
{
var textDto = DictionaryTranslationFactory.BuildDto(translation, dictionaryItem.Key);
translation.Id = Convert.ToInt32(Database.Insert(textDto));
translation.Key = dictionaryItem.Key;
}
dictionaryItem.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDictionaryItem entity)
{
entity.UpdatingEntity();
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
var dto = DictionaryItemFactory.BuildDto(entity);
Database.Update(dto);
foreach (var translation in entity.Translations)
{
var textDto = DictionaryTranslationFactory.BuildDto(translation, entity.Key);
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
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.Key));
}
protected override void PersistDeletedItem(IDictionaryItem entity)
{
RecursiveDelete(entity.Key);
Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key });
Database.Delete("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(entity.Key));
entity.DeleteDate = DateTime.Now;
}
private void RecursiveDelete(Guid parentId)
{
var list = Database.Fetch("WHERE parent = @ParentId", new { ParentId = parentId });
foreach (var dto in list)
{
RecursiveDelete(dto.UniqueId);
Database.Delete("WHERE UniqueId = @Id", new { Id = dto.UniqueId });
Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey(dto.UniqueId));
}
}
#endregion
protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
{
var entity = DictionaryItemFactory.BuildEntity(dto);
entity.Translations = dto.LanguageTextDtos.EmptyNull()
.Where(x => x.LanguageId > 0)
.Select(x => DictionaryTranslationFactory.BuildEntity(x, dto.UniqueId))
.ToList();
return entity;
}
public IDictionaryItem Get(Guid uniqueId)
{
var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches, _loggerFactory.CreateLogger());
return uniqueIdRepo.Get(uniqueId);
}
public IDictionaryItem Get(string key)
{
var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches, _loggerFactory.CreateLogger());
return keyRepo.Get(key);
}
private IEnumerable GetRootDictionaryItems()
{
var query = Query().Where(x => x.ParentId == null);
return Get(query);
}
public Dictionary GetDictionaryItemKeyMap()
{
var columns = new[] { "key", "id" }.Select(x => (object) SqlSyntax.GetQuotedColumnName(x)).ToArray();
var sql = Sql().Select(columns).From();
return Database.Fetch(sql).ToDictionary(x => x.Key, x => x.Id);
}
private class DictionaryItemKeyIdDto
{
public string Key { get; set; }
public Guid Id { get; set; }
}
public IEnumerable 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>> getItemsFromParents = guids =>
{
return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
.Select(group =>
{
var sqlClause = GetBaseQuery(false)
.Where(x => x.Parent != null)
.WhereIn(x => x.Parent, group);
var translator = new SqlTranslator(sqlClause, Query());
var sql = translator.Translate();
sql.OrderBy(x => x.UniqueId);
return Database
.FetchOneToMany(x=> x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
});
};
var childItems = parentId.HasValue == false
? new[] { GetRootDictionaryItems() }
: getItemsFromParents(new[] { parentId.Value });
return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items);
}
private class DictionaryByUniqueIdRepository : SimpleGetRepository
{
private readonly DictionaryRepository _dictionaryRepository;
public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
{
_dictionaryRepository = dictionaryRepository;
}
protected override IEnumerable PerformFetch(Sql sql)
{
return Database
.FetchOneToMany(x => x.LanguageTextDtos, sql);
}
protected override Sql 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 CreateCachePolicy()
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options);
}
}
private class DictionaryByKeyRepository : SimpleGetRepository
{
private readonly DictionaryRepository _dictionaryRepository;
public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
{
_dictionaryRepository = dictionaryRepository;
}
protected override IEnumerable PerformFetch(Sql sql)
{
return Database
.FetchOneToMany(x => x.LanguageTextDtos, sql);
}
protected override Sql 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)
{
return new { id = id };
}
protected override string GetWhereInClauseForGetAll()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)";
}
protected override IRepositoryCachePolicy CreateCachePolicy()
{
var options = new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options);
}
}
}
}