using System; using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Strings; namespace Umbraco.Core.Persistence.Repositories.Implement { internal class MacroRepository : NPocoRepositoryBase, IMacroRepository { private readonly IShortStringHelper _shortStringHelper; public MacroRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IShortStringHelper shortStringHelper) : base(scopeAccessor, cache, logger) { _shortStringHelper = shortStringHelper; } protected override IMacro PerformGet(int id) { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { id }); return GetBySql(sql); } public IMacro Get(Guid id) { var sql = GetBaseQuery().Where("uniqueId=@Id", new { Id = id }); return GetBySql(sql); } private IMacro GetBySql(Sql sql) { var macroDto = Database .FetchOneToMany(x => x.MacroPropertyDtos, sql) .FirstOrDefault(); if (macroDto == null) return null; var entity = MacroFactory.BuildEntity(_shortStringHelper, macroDto); // reset dirty initial properties (U4-1946) ((BeingDirtyBase)entity).ResetDirtyProperties(false); return entity; } public IEnumerable GetMany(params Guid[] ids) { return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); } public bool Exists(Guid id) { return Get(id) != null; } protected override IEnumerable PerformGetAll(params int[] ids) { return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds(); } private IEnumerable GetAllNoIds() { var sql = GetBaseQuery(false) //must be sorted this way for the relator to work .OrderBy(x => x.Id); return Database .FetchOneToMany(x => x.MacroPropertyDtos, sql) .Transform(ConvertFromDtos) .ToArray(); // do it now and once } private IEnumerable ConvertFromDtos(IEnumerable dtos) { foreach (var entity in dtos.Select(x => MacroFactory.BuildEntity(_shortStringHelper, x))) { // reset dirty initial properties (U4-1946) ((BeingDirtyBase)entity).ResetDirtyProperties(false); yield return entity; } } protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); return Database .FetchOneToMany(x => x.MacroPropertyDtos, sql) .Select(x => Get(x.Id)); } protected override Sql GetBaseQuery(bool isCount) { return isCount ? Sql().SelectCount().From() : GetBaseQuery(); } private Sql GetBaseQuery() { return Sql() .SelectAll() .From() .LeftJoin() .On(left => left.Id, right => right.Macro); } protected override string GetBaseWhereClause() { return "cmsMacro.id = @id"; } protected override IEnumerable GetDeleteClauses() { var list = new List { "DELETE FROM cmsMacroProperty WHERE macro = @id", "DELETE FROM cmsMacro WHERE id = @id" }; return list; } protected override Guid NodeObjectTypeId => throw new NotImplementedException(); protected override void PersistNewItem(IMacro entity) { entity.AddingEntity(); var dto = MacroFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; foreach (var propDto in dto.MacroPropertyDtos) { //need to set the id explicitly here propDto.Macro = id; var propId = Convert.ToInt32(Database.Insert(propDto)); entity.Properties[propDto.Alias].Id = propId; } entity.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IMacro entity) { entity.UpdatingEntity(); var dto = MacroFactory.BuildDto(entity); Database.Update(dto); //update the properties if they've changed var macro = (Macro)entity; if (macro.IsPropertyDirty("Properties") || macro.Properties.Values.Any(x => x.IsDirty())) { var ids = dto.MacroPropertyDtos.Where(x => x.Id > 0).Select(x => x.Id).ToArray(); if (ids.Length > 0) Database.Delete("WHERE macro=@macro AND id NOT IN (@ids)", new { macro = dto.Id, ids }); else Database.Delete("WHERE macro=@macro", new { macro = dto.Id }); // detect new aliases, replace with temp aliases // this ensures that we don't have collisions, ever var aliases = new Dictionary(); foreach (var propDto in dto.MacroPropertyDtos) { var prop = macro.Properties.Values.FirstOrDefault(x => x.Id == propDto.Id); if (prop == null) throw new Exception("oops: property."); if (propDto.Id == 0 || prop.IsPropertyDirty("Alias")) { var tempAlias = Guid.NewGuid().ToString("N").Substring(0, 8); aliases[tempAlias] = propDto.Alias; propDto.Alias = tempAlias; } } // insert or update existing properties, with temp aliases foreach (var propDto in dto.MacroPropertyDtos) { if (propDto.Id == 0) { // insert propDto.Id = Convert.ToInt32(Database.Insert(propDto)); macro.Properties[aliases[propDto.Alias]].Id = propDto.Id; } else { // update var property = macro.Properties.Values.FirstOrDefault(x => x.Id == propDto.Id); if (property == null) throw new Exception("oops: property."); if (property.IsDirty()) Database.Update(propDto); } } // replace the temp aliases with the real ones foreach (var propDto in dto.MacroPropertyDtos) { if (aliases.ContainsKey(propDto.Alias) == false) continue; propDto.Alias = aliases[propDto.Alias]; Database.Update(propDto); } } entity.ResetDirtyProperties(); } } }