diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs index 9bf9c365f077..1d8b19f88945 100644 --- a/src/Umbraco.Core/Collections/StackQueue.cs +++ b/src/Umbraco.Core/Collections/StackQueue.cs @@ -1,60 +1,39 @@ using System.Collections.Generic; -namespace Umbraco.Core.Collections +namespace Umbraco.Cms.Core.Collections { /// - /// Collection that can be both a queue and a stack. + /// Collection that can be both a queue and a stack. /// /// public class StackQueue { - private readonly LinkedList _linkedList = new LinkedList(); + private readonly LinkedList _linkedList = new(); - public void Clear() - { - _linkedList.Clear(); - } + public int Count => _linkedList.Count; - public void Push(T obj) - { - _linkedList.AddFirst(obj); - } + public void Clear() => _linkedList.Clear(); - public void Enqueue(T obj) - { - _linkedList.AddFirst(obj); - } + public void Push(T obj) => _linkedList.AddFirst(obj); + + public void Enqueue(T obj) => _linkedList.AddFirst(obj); public T Pop() { - var obj = _linkedList.First.Value; + T obj = _linkedList.First.Value; _linkedList.RemoveFirst(); return obj; } public T Dequeue() { - var obj = _linkedList.Last.Value; + T obj = _linkedList.Last.Value; _linkedList.RemoveLast(); return obj; } - public T PeekStack() - { - return _linkedList.First.Value; - } + public T PeekStack() => _linkedList.First.Value; - public T PeekQueue() - { - return _linkedList.Last.Value; - } - - public int Count - { - get - { - return _linkedList.Count; - } - } + public T PeekQueue() => _linkedList.Last.Value; } } diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs index 443032c67ae8..f07867cccc83 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -1,38 +1,43 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Data.SqlClient; using System.Linq; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Infrastructure.Persistence; namespace Umbraco.Extensions { /// - /// Provides extension methods to NPoco Database class. + /// Provides extension methods to NPoco Database class. /// public static partial class NPocoDatabaseExtensions { /// - /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction + /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the + /// underlying RetryDbConnection and ProfiledDbTransaction /// /// - /// This is required to use NPoco's own method because we use wrapped DbConnection and DbTransaction instances. - /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for - /// any other database type and in which case will just insert records one at a time. - /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods - /// do not handle this scenario. + /// This is required to use NPoco's own method because we use + /// wrapped DbConnection and DbTransaction instances. + /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for + /// bulk inserting of records for + /// any other database type and in which case will just insert records one at a time. + /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own + /// BulkInsertRecords methods + /// do not handle this scenario. /// public static void ConfigureNPocoBulkExtensions() { - SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection(dbConn); SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction(dbTran); } /// - /// Creates bulk-insert commands. + /// Creates bulk-insert commands. /// /// The type of the records. /// The database. @@ -40,17 +45,22 @@ public static void ConfigureNPocoBulkExtensions() /// The sql commands to execute. internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase database, T[] records) { - if (database?.Connection == null) throw new ArgumentException("Null database?.connection.", nameof(database)); + if (database?.Connection == null) + { + throw new ArgumentException("Null database?.connection.", nameof(database)); + } - var pocoData = database.PocoDataFactory.ForType(typeof(T)); + PocoData pocoData = database.PocoDataFactory.ForType(typeof(T)); // get columns to include, = number of parameters per row - var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); + KeyValuePair[] columns = + pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); var paramsPerRecord = columns.Length; // format columns to sql var tableName = database.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); - var columnNames = string.Join(", ", columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); + var columnNames = string.Join(", ", + columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); // example: // assume 4168 records, each record containing 8 fields, ie 8 command parameters @@ -58,7 +68,9 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase // Math.Floor(2100 / 8) = 262 record per command // 4168 / 262 = 15.908... = there will be 16 command in total // (if we have disabled db parameters, then all records will be included, in only one command) - var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord)); + var recordsPerCommand = paramsPerRecord == 0 + ? int.MaxValue + : Convert.ToInt32(Math.Floor((double)Constants.Sql.MaxParameterCount / paramsPerRecord)); var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand)); var commands = new IDbCommand[commandsCount]; @@ -67,23 +79,27 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase var prefix = database.DatabaseType.GetParameterPrefix(database.ConnectionString); for (var commandIndex = 0; commandIndex < commandsCount; commandIndex++) { - var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); + DbCommand command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); var parameterIndex = 0; var commandRecords = Math.Min(recordsPerCommand, recordsLeftToInsert); var recordsValues = new string[commandRecords]; - for (var commandRecordIndex = 0; commandRecordIndex < commandRecords; commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) + for (var commandRecordIndex = 0; + commandRecordIndex < commandRecords; + commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) { - var record = records[recordsIndex]; + T record = records[recordsIndex]; var recordValues = new string[columns.Length]; for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++) { database.AddParameter(command, columns[columnIndex].Value.GetValue(record)); recordValues[columnIndex] = prefix + parameterIndex++; } + recordsValues[commandRecordIndex] = "(" + string.Join(",", recordValues) + ")"; } - command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; + command.CommandText = + $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; commands[commandIndex] = command; } @@ -91,19 +107,14 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase } /// - /// Determines whether a column should be part of a bulk-insert. + /// Determines whether a column should be part of a bulk-insert. /// /// The PocoData object corresponding to the record's type. /// The column. /// A value indicating whether the column should be part of the bulk-insert. /// Columns that are primary keys and auto-incremental, or result columns, are excluded from bulk-inserts. - public static bool IncludeColumn(PocoData pocoData, KeyValuePair column) - { - return column.Value.ResultColumn == false - && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); - } - - - + public static bool IncludeColumn(PocoData pocoData, KeyValuePair column) => + column.Value.ResultColumn == false + && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index c439c1a80d18..e3685dd32ccf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -3,6 +3,7 @@ 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.Persistence.Querying; @@ -16,26 +17,47 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Represents the NPoco implementation of . + /// Represents the NPoco implementation of . /// internal class AuditEntryRepository : EntityRepositoryBase, IAuditEntryRepository { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public AuditEntryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + } + + /// + public IEnumerable GetPage(long pageIndex, int pageCount, out long records) + { + Sql sql = Sql() + .Select() + .From() + .OrderByDescending(x => x.EventDateUtc); + + Page page = Database.Page(pageIndex + 1, pageCount, sql); + records = page.TotalItems; + return page.Items.Select(AuditEntryFactory.BuildEntity); + } + + /// + public bool IsAvailable() + { + var tables = SqlSyntax.GetTablesInSchema(Database).ToArray(); + return tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry); + } /// protected override IAuditEntry PerformGet(int id) { - var sql = Sql() + Sql sql = Sql() .Select() .From() .Where(x => x.Id == id); - var dto = Database.FirstOrDefault(sql); + AuditEntryDto dto = Database.FirstOrDefault(sql); return dto == null ? null : AuditEntryFactory.BuildEntity(dto); } @@ -44,7 +66,7 @@ protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Length == 0) { - var sql = Sql() + Sql sql = Sql() .Select() .From(); @@ -53,9 +75,9 @@ protected override IEnumerable PerformGetAll(params int[] ids) var entries = new List(); - foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) + foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) { - var sql = Sql() + Sql sql = Sql() .Select() .From() .WhereIn(x => x.Id, group); @@ -69,68 +91,41 @@ protected override IEnumerable PerformGetAll(params int[] ids) /// protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(false); + Sql sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + Sql sql = translator.Translate(); return Database.Fetch(sql).Select(AuditEntryFactory.BuildEntity); } /// protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); sql = isCount ? sql.SelectCount() : sql.Select(); sql = sql.From(); return sql; } /// - protected override string GetBaseWhereClause() - { - return $"{Cms.Core.Constants.DatabaseSchema.Tables.AuditEntry}.id = @id"; - } + protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.AuditEntry}.id = @id"; /// - protected override IEnumerable GetDeleteClauses() - { + protected override IEnumerable GetDeleteClauses() => throw new NotSupportedException("Audit entries cannot be deleted."); - } /// protected override void PersistNewItem(IAuditEntry entity) { entity.AddingEntity(); - var dto = AuditEntryFactory.BuildDto(entity); + AuditEntryDto dto = AuditEntryFactory.BuildDto(entity); Database.Insert(dto); entity.Id = dto.Id; entity.ResetDirtyProperties(); } /// - protected override void PersistUpdatedItem(IAuditEntry entity) - { + protected override void PersistUpdatedItem(IAuditEntry entity) => throw new NotSupportedException("Audit entries cannot be updated."); - } - - /// - public IEnumerable GetPage(long pageIndex, int pageCount, out long records) - { - var sql = Sql() - .Select() - .From() - .OrderByDescending(x => x.EventDateUtc); - - var page = Database.Page(pageIndex + 1, pageCount, sql); - records = page.TotalItems; - return page.Items.Select(AuditEntryFactory.BuildEntity); - } - - /// - public bool IsAvailable() - { - var tables = SqlSyntax.GetTablesInSchema(Database).ToArray(); - return tables.InvariantContains(Cms.Core.Constants.DatabaseSchema.Tables.AuditEntry); - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index ac69d02587aa..b30c5ae1a44d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -3,6 +3,7 @@ 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.Persistence.Querying; @@ -14,47 +15,55 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// An internal repository for managing entity containers such as doc type, media type, data type containers. + /// An internal repository for managing entity containers such as doc type, media type, data type containers. /// internal class EntityContainerRepository : EntityRepositoryBase, IEntityContainerRepository { - private readonly Guid _containerObjectType; - - public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, Guid containerObjectType) + public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger logger, Guid containerObjectType) : base(scopeAccessor, cache, logger) { - var allowedContainers = new[] { Cms.Core.Constants.ObjectTypes.DocumentTypeContainer, Cms.Core.Constants.ObjectTypes.MediaTypeContainer, Cms.Core.Constants.ObjectTypes.DataTypeContainer }; - _containerObjectType = containerObjectType; - if (allowedContainers.Contains(_containerObjectType) == false) - throw new InvalidOperationException("No container type exists with ID: " + _containerObjectType); + Guid[] allowedContainers = new[] + { + Constants.ObjectTypes.DocumentTypeContainer, Constants.ObjectTypes.MediaTypeContainer, + Constants.ObjectTypes.DataTypeContainer + }; + NodeObjectTypeId = containerObjectType; + if (allowedContainers.Contains(NodeObjectTypeId) == false) + { + throw new InvalidOperationException("No container type exists with ID: " + NodeObjectTypeId); + } } + protected Guid NodeObjectTypeId { get; } + // never cache - protected override IRepositoryCachePolicy CreateCachePolicy() - { - return NoCacheRepositoryCachePolicy.Instance; - } + protected override IRepositoryCachePolicy CreateCachePolicy() => + NoCacheRepositoryCachePolicy.Instance; protected override EntityContainer PerformGet(int id) { - var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); + Sql sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { id, NodeObjectType = NodeObjectTypeId }); - var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + NodeDto nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } // temp - so we don't have to implement GetByQuery public EntityContainer Get(Guid id) { - var sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); + Sql sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); - var nodeDto = Database.Fetch(sql).FirstOrDefault(); + NodeDto nodeDto = Database.Fetch(sql).FirstOrDefault(); return nodeDto == null ? null : CreateEntity(nodeDto); } public IEnumerable Get(string name, int level) { - var sql = GetBaseQuery(false).Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", new { name, level, umbracoObjectTypeId = NodeObjectTypeId }); + Sql sql = GetBaseQuery(false) + .Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", + new { name, level, umbracoObjectTypeId = NodeObjectTypeId }); return Database.Fetch(sql).Select(CreateEntity); } @@ -63,38 +72,38 @@ protected override IEnumerable PerformGetAll(params int[] ids) if (ids.Any()) { return Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => - GetBaseQuery(false) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .WhereIn(x => x.NodeId, batch)) + GetBaseQuery(false) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .WhereIn(x => x.NodeId, batch)) .Select(CreateEntity); } // else - var sql = GetBaseQuery(false) + Sql sql = GetBaseQuery(false) .Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId }) .OrderBy(x => x.Level); return Database.Fetch(sql).Select(CreateEntity); } - protected override IEnumerable PerformGetByQuery(IQuery query) - { + protected override IEnumerable PerformGetByQuery(IQuery query) => throw new NotImplementedException(); - } private static EntityContainer CreateEntity(NodeDto nodeDto) { if (nodeDto.NodeObjectType.HasValue == false) + { throw new InvalidOperationException("Node with id " + nodeDto.NodeId + " has no object type."); + } // throws if node is not a container - var containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); + Guid containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); var entity = new EntityContainer(nodeDto.NodeId, nodeDto.UniqueId, nodeDto.ParentId, nodeDto.Path, nodeDto.Level, nodeDto.SortOrder, containedObjectType, - nodeDto.Text, nodeDto.UserId ?? Cms.Core.Constants.Security.UnknownUserId); + nodeDto.Text, nodeDto.UserId ?? Constants.Security.UnknownUserId); // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); @@ -104,11 +113,16 @@ private static EntityContainer CreateEntity(NodeDto nodeDto) protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); if (isCount) + { sql.SelectCount(); + } else + { sql.SelectAll(); + } + sql.From(); return sql; } @@ -117,23 +131,29 @@ protected override Sql GetBaseQuery(bool isCount) protected override IEnumerable GetDeleteClauses() => throw new NotImplementedException(); - protected Guid NodeObjectTypeId => _containerObjectType; - protected override void PersistDeletedItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); - if (nodeDto == null) return; + if (nodeDto == null) + { + return; + } // move children to the parent so they are not orphans - var childDtos = Database.Fetch(Sql().SelectAll() + List childDtos = Database.Fetch(Sql().SelectAll() .From() - .Where("parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", + .Where( + "parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", new { parentID = entity.Id, @@ -141,7 +161,7 @@ protected override void PersistDeletedItem(EntityContainer entity) containerObjectType = entity.ContainerObjectType })); - foreach (var childDto in childDtos) + foreach (NodeDto childDto in childDtos) { childDto.ParentId = nodeDto.ParentId; Database.Update(childDto); @@ -155,31 +175,51 @@ protected override void PersistDeletedItem(EntityContainer entity) protected override void PersistNewItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); + if (entity.Name == null) + { + throw new InvalidOperationException("Entity name can't be null."); + } + + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new InvalidOperationException( + "Entity name can't be empty or consist only of white-space characters."); + } + entity.Name = entity.Name.Trim(); // guard against duplicates - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.ParentId == entity.ParentId && dto.Text == entity.Name && + dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto != null) + { throw new InvalidOperationException("A container with the same name already exists."); + } // create var level = 0; var path = "-1"; if (entity.ParentId > -1) { - var parentDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto parentDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parentDto == null) + { throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); + } level = parentDto.Level; path = parentDto.Path; @@ -203,7 +243,7 @@ protected override void PersistNewItem(EntityContainer entity) // insert, get the id, update the path with the id var id = Convert.ToInt32(Database.Insert(nodeDto)); nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; - Database.Save(nodeDto); + Database.Save(nodeDto); // refresh the entity entity.Id = id; @@ -218,26 +258,45 @@ protected override void PersistNewItem(EntityContainer entity) // protected override void PersistUpdatedItem(EntityContainer entity) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); + if (entity == null) + { + throw new ArgumentNullException(nameof(entity)); + } + EnsureContainerType(entity); - if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); + if (entity.Name == null) + { + throw new InvalidOperationException("Entity name can't be null."); + } + + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new InvalidOperationException( + "Entity name can't be empty or consist only of white-space characters."); + } + entity.Name = entity.Name.Trim(); // find container to update - var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto == null) + { throw new InvalidOperationException("Could not find container with id " + entity.Id); + } // guard against duplicates - var dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() + NodeDto dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.ParentId == entity.ParentId && dto.Text == entity.Name && + dto.NodeObjectType == entity.ContainerObjectType)); if (dupNodeDto != null && dupNodeDto.NodeId != nodeDto.NodeId) + { throw new InvalidOperationException("A container with the same name already exists."); + } // update nodeDto.Text = entity.Name; @@ -247,16 +306,21 @@ protected override void PersistUpdatedItem(EntityContainer entity) nodeDto.Path = "-1"; if (entity.ParentId > -1) { - var parent = Database.FirstOrDefault( Sql().SelectAll() + NodeDto parent = Database.FirstOrDefault(Sql().SelectAll() .From() - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + .Where(dto => + dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parent == null) - throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); + { + throw new InvalidOperationException( + "Could not find parent container with id " + entity.ParentId); + } nodeDto.Level = Convert.ToInt16(parent.Level + 1); nodeDto.Path = parent.Path + "," + nodeDto.NodeId; } + nodeDto.ParentId = entity.ParentId; } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index 1589c9d52ef0..79e6f732a2a9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence; @@ -14,38 +15,37 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Provides a base class to all based repositories. + /// Provides a base class to all based repositories. /// /// The type of the entity's unique identifier. /// The type of the entity managed by this repository. public abstract class EntityRepositoryBase : RepositoryBase, IReadWriteQueryRepository where TEntity : class, IEntity { + private static RepositoryCachePolicyOptions s_defaultOptions; private IRepositoryCachePolicy _cachePolicy; private IQuery _hasIdQuery; - private static RepositoryCachePolicyOptions s_defaultOptions; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger) - : base(scopeAccessor, appCaches) - { + protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, + ILogger> logger) + : base(scopeAccessor, appCaches) => Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } /// - /// Gets the logger + /// Gets the logger /// protected ILogger> Logger { get; } /// - /// Gets the isolated cache for the + /// Gets the isolated cache for the /// protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); /// - /// Gets the isolated cache. + /// Gets the isolated cache. /// /// Depends on the ambient scope cache mode. protected IAppPolicyCache IsolatedCache @@ -67,19 +67,20 @@ protected IAppPolicyCache IsolatedCache } /// - /// Gets the default + /// Gets the default /// protected virtual RepositoryCachePolicyOptions DefaultOptions => s_defaultOptions ?? (s_defaultOptions - = new RepositoryCachePolicyOptions(() => - { - // get count of all entities of current type (TEntity) to ensure cached result is correct - // create query once if it is needed (no need for locking here) - query is static! - IQuery query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); - return PerformCount(query); - })); + = new RepositoryCachePolicyOptions(() => + { + // get count of all entities of current type (TEntity) to ensure cached result is correct + // create query once if it is needed (no need for locking here) - query is static! + IQuery query = _hasIdQuery ?? + (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); + return PerformCount(query); + })); /// - /// Gets the repository cache policy + /// Gets the repository cache policy /// protected IRepositoryCachePolicy CachePolicy { @@ -110,21 +111,9 @@ protected IRepositoryCachePolicy CachePolicy } /// - /// Get the entity id for the - /// - protected virtual TId GetEntityId(TEntity entity) - => (TId)(object)entity.Id; - - /// - /// Create the repository cache policy + /// Adds or Updates an entity of type TEntity /// - protected virtual IRepositoryCachePolicy CreateCachePolicy() - => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); - - /// - /// Adds or Updates an entity of type TEntity - /// - /// This method is backed by an cache + /// This method is backed by an cache public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) @@ -138,64 +127,19 @@ public virtual void Save(TEntity entity) } /// - /// Deletes the passed in entity + /// Deletes the passed in entity /// public virtual void Delete(TEntity entity) => CachePolicy.Delete(entity, PersistDeletedItem); - protected abstract TEntity PerformGet(TId id); - - protected abstract IEnumerable PerformGetAll(params TId[] ids); - - protected abstract IEnumerable PerformGetByQuery(IQuery query); - - protected abstract void PersistNewItem(TEntity item); - - protected abstract void PersistUpdatedItem(TEntity item); - - // TODO: obsolete, use QueryType instead everywhere like GetBaseQuery(QueryType queryType); - protected abstract Sql GetBaseQuery(bool isCount); - - protected abstract string GetBaseWhereClause(); - - protected abstract IEnumerable GetDeleteClauses(); - - protected virtual bool PerformExists(TId id) - { - var sql = GetBaseQuery(true); - sql.Where(GetBaseWhereClause(), new { id = id }); - var count = Database.ExecuteScalar(sql); - return count == 1; - } - - protected virtual int PerformCount(IQuery query) - { - var sqlClause = GetBaseQuery(true); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - return Database.ExecuteScalar(sql); - } - - protected virtual void PersistDeletedItem(TEntity entity) - { - var deletes = GetDeleteClauses(); - foreach (var delete in deletes) - { - Database.Execute(delete, new { id = GetEntityId(entity) }); - } - - entity.DeleteDate = DateTime.Now; - } - /// - /// Gets an entity by the passed in Id utilizing the repository's cache policy + /// Gets an entity by the passed in Id utilizing the repository's cache policy /// public TEntity Get(TId id) => CachePolicy.Get(id, PerformGet, PerformGetAll); /// - /// Gets all entities of type TEntity or a list according to the passed in Ids + /// Gets all entities of type TEntity or a list according to the passed in Ids /// public IEnumerable GetMany(params TId[] ids) { @@ -215,7 +159,7 @@ public IEnumerable GetMany(params TId[] ids) } var entities = new List(); - foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) + foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount)) { entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll)); } @@ -224,22 +168,79 @@ public IEnumerable GetMany(params TId[] ids) } /// - /// Gets a list of entities by the passed in query + /// Gets a list of entities by the passed in query /// public IEnumerable Get(IQuery query) => PerformGetByQuery(query) .WhereNotNull(); // ensure we don't include any null refs in the returned collection! /// - /// Returns a boolean indicating whether an entity with the passed Id exists + /// Returns a boolean indicating whether an entity with the passed Id exists /// public bool Exists(TId id) => CachePolicy.Exists(id, PerformExists, PerformGetAll); /// - /// Returns an integer with the count of entities found with the passed in query + /// Returns an integer with the count of entities found with the passed in query /// public int Count(IQuery query) => PerformCount(query); + + /// + /// Get the entity id for the + /// + protected virtual TId GetEntityId(TEntity entity) + => (TId)(object)entity.Id; + + /// + /// Create the repository cache policy + /// + protected virtual IRepositoryCachePolicy CreateCachePolicy() + => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + + protected abstract TEntity PerformGet(TId id); + + protected abstract IEnumerable PerformGetAll(params TId[] ids); + + protected abstract IEnumerable PerformGetByQuery(IQuery query); + + protected abstract void PersistNewItem(TEntity item); + + protected abstract void PersistUpdatedItem(TEntity item); + + // TODO: obsolete, use QueryType instead everywhere like GetBaseQuery(QueryType queryType); + protected abstract Sql GetBaseQuery(bool isCount); + + protected abstract string GetBaseWhereClause(); + + protected abstract IEnumerable GetDeleteClauses(); + + protected virtual bool PerformExists(TId id) + { + Sql sql = GetBaseQuery(true); + sql.Where(GetBaseWhereClause(), new { id }); + var count = Database.ExecuteScalar(sql); + return count == 1; + } + + protected virtual int PerformCount(IQuery query) + { + Sql sqlClause = GetBaseQuery(true); + var translator = new SqlTranslator(sqlClause, query); + Sql sql = translator.Translate(); + + return Database.ExecuteScalar(sql); + } + + protected virtual void PersistDeletedItem(TEntity entity) + { + IEnumerable deletes = GetDeleteClauses(); + foreach (var delete in deletes) + { + Database.Execute(delete, new { id = GetEntityId(entity) }); + } + + entity.DeleteDate = DateTime.Now; + } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 6485dc286a6e..4031971ddcc7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; @@ -26,17 +27,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// Represents a repository for doing CRUD operations for + /// Represents a repository for doing CRUD operations for /// public class MemberRepository : ContentRepositoryBase, IMemberRepository { - private readonly MemberPasswordConfigurationSettings _passwordConfiguration; - private readonly IMemberTypeRepository _memberTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly IPasswordHasher _passwordHasher; private readonly IJsonSerializer _jsonSerializer; - private readonly IMemberGroupRepository _memberGroupRepository; private readonly IRepositoryCachePolicy _memberByUsernameCachePolicy; + private readonly IMemberGroupRepository _memberGroupRepository; + private readonly IMemberTypeRepository _memberTypeRepository; + private readonly MemberPasswordConfigurationSettings _passwordConfiguration; + private readonly IPasswordHasher _passwordHasher; + private readonly ITagRepository _tagRepository; private bool _passwordConfigInitialized; private string _passwordConfigJson; @@ -57,19 +58,22 @@ public MemberRepository( IJsonSerializer serializer, IEventAggregator eventAggregator, IOptions passwordConfiguration) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) + : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, + propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) { - _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); + _memberTypeRepository = + memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _passwordHasher = passwordHasher; _jsonSerializer = serializer; _memberGroupRepository = memberGroupRepository; _passwordConfiguration = passwordConfiguration.Value; - _memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + _memberByUsernameCachePolicy = + new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } /// - /// Returns a serialized dictionary of the password configuration that is stored against the member in the database + /// Returns a serialized dictionary of the password configuration that is stored against the member in the database /// private string DefaultPasswordConfigJson { @@ -95,17 +99,341 @@ private string DefaultPasswordConfigJson public override int RecycleBinId => throw new NotSupportedException(); + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + //get the group id + IQuery grpQry = Query().Where(group => group.Name.Equals(roleName)); + IMemberGroup memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); + if (memberGroup == null) + { + return Enumerable.Empty(); + } + + // get the members by username + IQuery query = Query(); + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(usernameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(usernameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(usernameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(usernameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); + } + + IMember[] matchedMembers = Get(query).ToArray(); + + var membersInGroup = new List(); + + //then we need to filter the matched members that are in the role + foreach (IEnumerable group in matchedMembers.Select(x => x.Id) + .InGroupsOf(Constants.Sql.MaxParameterCount)) + { + Sql sql = Sql().SelectAll().From() + .Where(dto => dto.MemberGroup == memberGroup.Id) + .WhereIn(dto => dto.Member, group); + + var memberIdsInGroup = Database.Fetch(sql) + .Select(x => x.Member).ToArray(); + + membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); + } + + return membersInGroup; + } + + /// + /// Get all members in a specific group + /// + /// + /// + public IEnumerable GetByMemberGroup(string groupName) + { + IQuery grpQry = Query().Where(group => group.Name.Equals(groupName)); + IMemberGroup memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); + if (memberGroup == null) + { + return Enumerable.Empty(); + } + + Sql subQuery = Sql().Select("Member").From() + .Where(dto => dto.MemberGroup == memberGroup.Id); + + Sql sql = GetBaseQuery(false) + // TODO: An inner join would be better, though I've read that the query optimizer will always turn a + // subquery with an IN clause into an inner join anyways. + .Append("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return MapDtosToContent(Database.Fetch(sql)); + } + + public bool Exists(string username) + { + Sql sql = Sql() + .SelectCount() + .From() + .Where(x => x.LoginName == username); + + return Database.ExecuteScalar(sql) > 0; + } + + public int GetCountByQuery(IQuery query) + { + Sql sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + Sql sql = translator.Translate(); + + //get the COUNT base query + Sql fullSql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); + + return Database.ExecuteScalar(fullSql); + } + + /// + public void SetLastLogin(string username, DateTime date) + { + // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock + // table. However due to the data that we are updating which relies on version data we cannot update this data + // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation + // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying + // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just + // maintains a single row and it isn't deleted/re-inserted. + // So the important part here is the ForUpdate() call on the select to fetch the property data to update. + + // Update the cms property value for the member + + SqlTemplate sqlSelectTemplateProperty = SqlContext.Templates.Get( + "Umbraco.Core.MemberRepository.SetLastLogin1", s => s + .Select(x => x.Id) + .From() + .InnerJoin() + .On((l, r) => l.Id == r.PropertyTypeId) + .InnerJoin() + .On((l, r) => l.Id == r.VersionId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) + .Where(x => x.LoginName == SqlTemplate.Arg("username")) + .ForUpdate()); + Sql sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member, + Constants.Conventions.Member.LastLoginDate, username); + + Sql update = Sql() + .Update(u => u + .Set(x => x.DateValue, date)) + .WhereIn(x => x.Id, sqlSelectProperty); + + Database.Execute(update); + + // Update the umbracoContentVersion value for the member + + SqlTemplate sqlSelectTemplateVersion = SqlContext.Templates.Get( + "Umbraco.Core.MemberRepository.SetLastLogin2", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.LoginName == SqlTemplate.Arg("username"))); + Sql sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username); + + Database.Execute(Sql() + .Update(u => u + .Set(x => x.VersionDate, date)) + .WhereIn(x => x.Id, sqlSelectVersion)); + } + + /// + /// Gets paged member results. + /// + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering) + { + Sql filterSql = null; + + if (filter != null) + { + filterSql = Sql(); + foreach (Tuple clause in filter.GetWhereClauses()) + { + filterSql = filterSql.Append($"AND ({clause.Item1})", clause.Item2); + } + } + + return GetPage(query, pageIndex, pageSize, out totalRecords, + x => MapDtosToContent(x), + filterSql, + ordering); + } + + public IMember GetByUsername(string username) => + _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); + + public int[] GetMemberIds(string[] usernames) + { + Guid memberObjectType = Constants.ObjectTypes.Member; + + Sql memberSql = Sql() + .Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new + { + /*usernames =*/ + usernames + }); + return Database.Fetch(memberSql).ToArray(); + } + + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) + { + if (ordering.OrderBy.InvariantEquals("email")) + { + return SqlSyntax.GetFieldName(x => x.Email); + } + + if (ordering.OrderBy.InvariantEquals("loginName")) + { + return SqlSyntax.GetFieldName(x => x.LoginName); + } + + if (ordering.OrderBy.InvariantEquals("userName")) + { + return SqlSyntax.GetFieldName(x => x.LoginName); + } + + if (ordering.OrderBy.InvariantEquals("updateDate")) + { + return SqlSyntax.GetFieldName(x => x.VersionDate); + } + + if (ordering.OrderBy.InvariantEquals("createDate")) + { + return SqlSyntax.GetFieldName(x => x.CreateDate); + } + + if (ordering.OrderBy.InvariantEquals("contentTypeAlias")) + { + return SqlSyntax.GetFieldName(x => x.Alias); + } + + return base.ApplySystemOrdering(ref sql, ordering); + } + + private IEnumerable MapDtosToContent(List dtos, bool withCache = false) + { + var temps = new List>(); + var contentTypes = new Dictionary(); + var content = new Member[dtos.Count]; + + for (var i = 0; i < dtos.Count; i++) + { + MemberDto dto = dtos[i]; + + if (withCache) + { + // if the cache contains the (proper version of the) item, use it + IMember cached = + IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); + if (cached != null && cached.VersionId == dto.ContentVersionDto.Id) + { + content[i] = (Member)cached; + continue; + } + } + + // else, need to build it + + // get the content type - the repository is full cache *but* still deep-clones + // whatever comes out of it, so use our own local index here to avoid this + var contentTypeId = dto.ContentDto.ContentTypeId; + if (contentTypes.TryGetValue(contentTypeId, out IMemberType contentType) == false) + { + contentTypes[contentTypeId] = contentType = _memberTypeRepository.Get(contentTypeId); + } + + Member c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); + + // need properties + var versionId = dto.ContentVersionDto.Id; + temps.Add(new TempContent(dto.NodeId, versionId, 0, contentType, c)); + } + + // load all properties for all documents from database in 1 query - indexed by version id + IDictionary properties = GetPropertyCollections(temps); + + // assign properties + foreach (TempContent temp in temps) + { + temp.Content.Properties = properties[temp.VersionId]; + + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); + } + + return content; + } + + private IMember MapDtoToContent(MemberDto dto) + { + IMemberType memberType = _memberTypeRepository.Get(dto.ContentDto.ContentTypeId); + Member member = ContentBaseFactory.BuildEntity(dto, memberType); + + // get properties - indexed by version id + var versionId = dto.ContentVersionDto.Id; + var temp = new TempContent(dto.ContentDto.NodeId, versionId, 0, memberType); + IDictionary properties = + GetPropertyCollections(new List> { temp }); + member.Properties = properties[versionId]; + + // reset dirty initial properties (U4-1946) + member.ResetDirtyProperties(false); + return member; + } + + private IMember PerformGetByUsername(string username) + { + IQuery query = Query().Where(x => x.Username.Equals(username)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByUsername(params string[] usernames) + { + IQuery query = Query().WhereIn(x => x.Username, usernames); + return PerformGetByQuery(query); + } + #region Repository Base - protected override Guid NodeObjectTypeId => Cms.Core.Constants.ObjectTypes.Member; + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.Member; protected override IMember PerformGet(int id) { - var sql = GetBaseQuery(QueryType.Single) + Sql sql = GetBaseQuery(QueryType.Single) .Where(x => x.NodeId == id) .SelectTop(1); - var dto = Database.Fetch(sql).FirstOrDefault(); + MemberDto dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : MapDtoToContent(dto); @@ -113,29 +441,31 @@ protected override IMember PerformGet(int id) protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(QueryType.Many); + Sql sql = GetBaseQuery(QueryType.Many); if (ids.Any()) + { sql.WhereIn(x => x.NodeId, ids); + } return MapDtosToContent(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) { - var baseQuery = GetBaseQuery(false); + Sql baseQuery = GetBaseQuery(false); // TODO: why is this different from content/media?! // check if the query is based on properties or not - var wheres = query.GetWhereClauses(); + IEnumerable> wheres = query.GetWhereClauses(); //this is a pretty rudimentary check but will work, we just need to know if this query requires property // level queries if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); + Sql sqlWithProps = GetNodeIdQueryWithPropertyData(); var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); + Sql sql = translator.Translate(); baseQuery.Append("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments) .OrderBy(x => x.SortOrder); @@ -145,22 +475,18 @@ protected override IEnumerable PerformGetByQuery(IQuery query) else { var translator = new SqlTranslator(baseQuery, query); - var sql = translator.Translate() + Sql sql = translator.Translate() .OrderBy(x => x.SortOrder); return MapDtosToContent(Database.Fetch(sql)); } - } - protected override Sql GetBaseQuery(QueryType queryType) - { - return GetBaseQuery(queryType, true); - } + protected override Sql GetBaseQuery(QueryType queryType) => GetBaseQuery(queryType, true); protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { - var sql = SqlContext.Sql(); + Sql sql = SqlContext.Sql(); switch (queryType) // TODO: pretend we still need these queries for now { @@ -187,52 +513,52 @@ protected virtual Sql GetBaseQuery(QueryType queryType, bool curren .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) // joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repos so we can query by content type there too. - .InnerJoin().On(left => left.ContentTypeId, right => right.NodeId); + .InnerJoin() + .On(left => left.ContentTypeId, right => right.NodeId); sql.Where(x => x.NodeObjectType == NodeObjectTypeId); if (current) + { sql.Where(x => x.Current); // always get the current version + } return sql; } // TODO: move that one up to Versionable! or better: kill it! - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); - } + protected override Sql GetBaseQuery(bool isCount) => + GetBaseQuery(isCount ? QueryType.Count : QueryType.Single); protected override string GetBaseWhereClause() // TODO: can we kill / refactor this? - { - return "umbracoNode.id = @id"; - } + => + "umbracoNode.id = @id"; // TODO: document/understand that one - protected Sql GetNodeIdQueryWithPropertyData() - { - return Sql() + protected Sql GetNodeIdQueryWithPropertyData() => + Sql() .Select("DISTINCT(umbracoNode.id)") .From() .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.ContentTypeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.NodeId == right.NodeId) .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - - .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin().On(left => left.DataTypeId, right => right.NodeId) - + .LeftJoin() + .On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin() + .On(left => left.DataTypeId, right => right.NodeId) .LeftJoin().On(x => x .Where((left, right) => left.PropertyTypeId == right.Id) .Where((left, right) => left.VersionId == right.Id)) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - } protected override IEnumerable GetDeleteClauses() { @@ -243,11 +569,13 @@ protected override IEnumerable GetDeleteClauses() "DELETE FROM umbracoRelation WHERE parentId = @id", "DELETE FROM umbracoRelation WHERE childId = @id", "DELETE FROM cmsTagRelationship WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.PropertyData + " WHERE versionId IN (SELECT id FROM " + Cms.Core.Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", + "DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + + " WHERE nodeId = @id)", "DELETE FROM cmsMember2MemberGroup WHERE Member = @id", "DELETE FROM cmsMember WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", - "DELETE FROM " + Cms.Core.Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", "DELETE FROM umbracoNode WHERE id = @id" }; return list; @@ -259,7 +587,7 @@ protected override IEnumerable GetDeleteClauses() public override IEnumerable GetAllVersions(int nodeId) { - var sql = GetBaseQuery(QueryType.Many, false) + Sql sql = GetBaseQuery(QueryType.Many, false) .Where(x => x.NodeId == nodeId) .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); @@ -269,10 +597,10 @@ public override IEnumerable GetAllVersions(int nodeId) public override IMember GetVersion(int versionId) { - var sql = GetBaseQuery(QueryType.Single) + Sql sql = GetBaseQuery(QueryType.Single) .Where(x => x.Id == versionId); - var dto = Database.Fetch(sql).FirstOrDefault(); + MemberDto dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : MapDtoToContent(dto); } @@ -346,13 +674,13 @@ protected override void PersistNewItem(IMember entity) entity.Level = level; // persist the content dto - var contentDto = memberDto.ContentDto; + ContentDto contentDto = memberDto.ContentDto; contentDto.NodeId = nodeDto.NodeId; Database.Insert(contentDto); // persist the content version dto // assumes a new version id and version date (modified date) has been set - var contentVersionDto = memberDto.ContentVersionDto; + ContentVersionDto contentVersionDto = memberDto.ContentVersionDto; contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = true; Database.Insert(contentVersionDto); @@ -365,8 +693,8 @@ protected override void PersistNewItem(IMember entity) // this will hash the guid with a salt so should be nicely random if (entity.RawPasswordValue.IsNullOrWhiteSpace()) { - - memberDto.Password = Cms.Core.Constants.Security.EmptyPasswordPrefix + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N")); + memberDto.Password = Constants.Security.EmptyPasswordPrefix + + _passwordHasher.HashPassword(Guid.NewGuid().ToString("N")); entity.RawPasswordValue = memberDto.Password; } @@ -496,298 +824,5 @@ protected override void PersistUpdatedItem(IMember entity) } #endregion - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - //get the group id - var grpQry = Query().Where(group => group.Name.Equals(roleName)); - var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); - if (memberGroup == null) - return Enumerable.Empty(); - - // get the members by username - var query = Query(); - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(usernameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(usernameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(usernameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(usernameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(matchType)); - } - var matchedMembers = Get(query).ToArray(); - - var membersInGroup = new List(); - - //then we need to filter the matched members that are in the role - foreach (var group in matchedMembers.Select(x => x.Id).InGroupsOf(Constants.Sql.MaxParameterCount)) - { - var sql = Sql().SelectAll().From() - .Where(dto => dto.MemberGroup == memberGroup.Id) - .WhereIn(dto => dto.Member, group); - - var memberIdsInGroup = Database.Fetch(sql) - .Select(x => x.Member).ToArray(); - - membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); - } - - return membersInGroup; - - } - - /// - /// Get all members in a specific group - /// - /// - /// - public IEnumerable GetByMemberGroup(string groupName) - { - var grpQry = Query().Where(group => group.Name.Equals(groupName)); - var memberGroup = _memberGroupRepository.Get(grpQry).FirstOrDefault(); - if (memberGroup == null) - return Enumerable.Empty(); - - var subQuery = Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); - - var sql = GetBaseQuery(false) - // TODO: An inner join would be better, though I've read that the query optimizer will always turn a - // subquery with an IN clause into an inner join anyways. - .Append("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return MapDtosToContent(Database.Fetch(sql)); - - } - - public bool Exists(string username) - { - var sql = Sql() - .SelectCount() - .From() - .Where(x => x.LoginName == username); - - return Database.ExecuteScalar(sql) > 0; - } - - public int GetCountByQuery(IQuery query) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - //get the COUNT base query - var fullSql = GetBaseQuery(true) - .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - - return Database.ExecuteScalar(fullSql); - } - - /// - public void SetLastLogin(string username, DateTime date) - { - // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock - // table. However due to the data that we are updating which relies on version data we cannot update this data - // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation - // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying - // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just - // maintains a single row and it isn't deleted/re-inserted. - // So the important part here is the ForUpdate() call on the select to fetch the property data to update. - - // Update the cms property value for the member - - var sqlSelectTemplateProperty = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin1", s => s - .Select(x => x.Id) - .From() - .InnerJoin().On((l, r) => l.Id == r.PropertyTypeId) - .InnerJoin().On((l, r) => l.Id == r.VersionId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) - .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) - .Where(x => x.LoginName == SqlTemplate.Arg("username")) - .ForUpdate()); - var sqlSelectProperty = sqlSelectTemplateProperty.Sql(Cms.Core.Constants.ObjectTypes.Member, Cms.Core.Constants.Conventions.Member.LastLoginDate, username); - - var update = Sql() - .Update(u => u - .Set(x => x.DateValue, date)) - .WhereIn(x => x.Id, sqlSelectProperty); - - Database.Execute(update); - - // Update the umbracoContentVersion value for the member - - var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s - .Select(x => x.Id) - .From() - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .InnerJoin().On((l, r) => l.NodeId == r.NodeId) - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) - .Where(x => x.LoginName == SqlTemplate.Arg("username"))); - var sqlSelectVersion = sqlSelectTemplateVersion.Sql(Cms.Core.Constants.ObjectTypes.Member, username); - - Database.Execute(Sql() - .Update(u => u - .Set(x => x.VersionDate, date)) - .WhereIn(x => x.Id, sqlSelectVersion)); - } - - /// - /// Gets paged member results. - /// - public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, - IQuery filter, - Ordering ordering) - { - Sql filterSql = null; - - if (filter != null) - { - filterSql = Sql(); - foreach (var clause in filter.GetWhereClauses()) - filterSql = filterSql.Append($"AND ({clause.Item1})", clause.Item2); - } - - return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), - filterSql, - ordering); - } - - protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) - { - if (ordering.OrderBy.InvariantEquals("email")) - return SqlSyntax.GetFieldName(x => x.Email); - - if (ordering.OrderBy.InvariantEquals("loginName")) - return SqlSyntax.GetFieldName(x => x.LoginName); - - if (ordering.OrderBy.InvariantEquals("userName")) - return SqlSyntax.GetFieldName(x => x.LoginName); - - if (ordering.OrderBy.InvariantEquals("updateDate")) - return SqlSyntax.GetFieldName(x => x.VersionDate); - - if (ordering.OrderBy.InvariantEquals("createDate")) - return SqlSyntax.GetFieldName(x => x.CreateDate); - - if (ordering.OrderBy.InvariantEquals("contentTypeAlias")) - return SqlSyntax.GetFieldName(x => x.Alias); - - return base.ApplySystemOrdering(ref sql, ordering); - } - - private IEnumerable MapDtosToContent(List dtos, bool withCache = false) - { - var temps = new List>(); - var contentTypes = new Dictionary(); - var content = new Member[dtos.Count]; - - for (var i = 0; i < dtos.Count; i++) - { - var dto = dtos[i]; - - if (withCache) - { - // if the cache contains the (proper version of the) item, use it - var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); - if (cached != null && cached.VersionId == dto.ContentVersionDto.Id) - { - content[i] = (Member)cached; - continue; - } - } - - // else, need to build it - - // get the content type - the repository is full cache *but* still deep-clones - // whatever comes out of it, so use our own local index here to avoid this - var contentTypeId = dto.ContentDto.ContentTypeId; - if (contentTypes.TryGetValue(contentTypeId, out var contentType) == false) - contentTypes[contentTypeId] = contentType = _memberTypeRepository.Get(contentTypeId); - - var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - - // need properties - var versionId = dto.ContentVersionDto.Id; - temps.Add(new TempContent(dto.NodeId, versionId, 0, contentType, c)); - } - - // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - - // assign properties - foreach (var temp in temps) - { - temp.Content.Properties = properties[temp.VersionId]; - - // reset dirty initial properties (U4-1946) - temp.Content.ResetDirtyProperties(false); - } - - return content; - } - - private IMember MapDtoToContent(MemberDto dto) - { - IMemberType memberType = _memberTypeRepository.Get(dto.ContentDto.ContentTypeId); - Member member = ContentBaseFactory.BuildEntity(dto, memberType); - - // get properties - indexed by version id - var versionId = dto.ContentVersionDto.Id; - var temp = new TempContent(dto.ContentDto.NodeId, versionId, 0, memberType); - var properties = GetPropertyCollections(new List> { temp }); - member.Properties = properties[versionId]; - - // reset dirty initial properties (U4-1946) - member.ResetDirtyProperties(false); - return member; - } - - public IMember GetByUsername(string username) - { - return _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); - } - - public int[] GetMemberIds(string[] usernames) - { - var memberObjectType = Cms.Core.Constants.ObjectTypes.Member; - - var memberSql = Sql() - .Select("umbracoNode.id") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .Where(x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { /*usernames =*/ usernames }); - return Database.Fetch(memberSql).ToArray(); - } - - private IMember PerformGetByUsername(string username) - { - var query = Query().Where(x => x.Username.Equals(username)); - return PerformGetByQuery(query).FirstOrDefault(); - } - - private IEnumerable PerformGetAllByUsername(params string[] usernames) - { - var query = Query().WhereIn(x => x.Username, usernames); - return PerformGetByQuery(query); - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index be98b7a7f60a..a1cfee69a905 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -15,30 +16,34 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { /// - /// A (sub) repository that exposes functionality to modify assigned permissions to a node + /// A (sub) repository that exposes functionality to modify assigned permissions to a node /// /// /// - /// This repo implements the base class so that permissions can be queued to be persisted - /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// This repo implements the base class so that permissions can be + /// queued to be persisted + /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// /// internal class PermissionRepository : EntityRepositoryBase where TEntity : class, IEntity { - public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) + public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger> logger) : base(scopeAccessor, cache, logger) - { } + { + } /// - /// Returns explicitly defined permissions for a user group for any number of nodes + /// Returns explicitly defined permissions for a user group for any number of nodes /// /// - /// The group ids to lookup permissions for + /// The group ids to lookup permissions for /// /// /// /// - /// This method will not support passing in more than 2000 group IDs when also passing in entity IDs. + /// This method will not support passing in more than 2000 group IDs when also passing in entity IDs. /// public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds) { @@ -46,15 +51,16 @@ public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, para if (entityIds.Length == 0) { - foreach (var group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount)) + foreach (IEnumerable group in groupIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { - var sql = Sql() + Sql sql = Sql() .SelectAll() .From() .Where(dto => group.Contains(dto.UserGroupId)); - var permissions = AmbientScope.Database.Fetch(sql); - foreach (var permission in ConvertToPermissionList(permissions)) + List permissions = + AmbientScope.Database.Fetch(sql); + foreach (EntityPermission permission in ConvertToPermissionList(permissions)) { result.Add(permission); } @@ -62,15 +68,18 @@ public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, para } else { - foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - groupIds.Length)) + foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - + groupIds.Length)) { - var sql = Sql() + Sql sql = Sql() .SelectAll() .From() - .Where(dto => groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId)); + .Where(dto => + groupIds.Contains(dto.UserGroupId) && group.Contains(dto.NodeId)); - var permissions = AmbientScope.Database.Fetch(sql); - foreach (var permission in ConvertToPermissionList(permissions)) + List permissions = + AmbientScope.Database.Fetch(sql); + foreach (EntityPermission permission in ConvertToPermissionList(permissions)) { result.Add(permission); } @@ -81,57 +90,60 @@ public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, para } /// - /// Returns permissions directly assigned to the content items for all user groups + /// Returns permissions directly assigned to the content items for all user groups /// /// /// public IEnumerable GetPermissionsForEntities(int[] entityIds) { - var sql = Sql() + Sql sql = Sql() .SelectAll() .From() .Where(dto => entityIds.Contains(dto.NodeId)) .OrderBy(dto => dto.NodeId); - var result = AmbientScope.Database.Fetch(sql); + List result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// - /// Returns permissions directly assigned to the content item for all user groups + /// Returns permissions directly assigned to the content item for all user groups /// /// /// public EntityPermissionCollection GetPermissionsForEntity(int entityId) { - var sql = Sql() + Sql sql = Sql() .SelectAll() .From() .Where(dto => dto.NodeId == entityId) .OrderBy(dto => dto.NodeId); - var result = AmbientScope.Database.Fetch(sql); + List result = AmbientScope.Database.Fetch(sql); return ConvertToPermissionList(result); } /// - /// Assigns the same permission set for a single group to any number of entities + /// Assigns the same permission set for a single group to any number of entities /// /// /// /// /// - /// This will first clear the permissions for this user and entities and recreate them + /// This will first clear the permissions for this user and entities and recreate them /// public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { if (entityIds.Length == 0) + { return; + } - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; - foreach (var group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount)) + var sql = + "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; + foreach (IEnumerable group in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { db.Execute(sql, new { groupId, nodeIds = group }); } @@ -143,9 +155,7 @@ public void ReplacePermissions(int groupId, IEnumerable permissions, param { toInsert.Add(new UserGroup2NodePermissionDto { - NodeId = e, - Permission = p.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId + NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }); } } @@ -154,46 +164,41 @@ public void ReplacePermissions(int groupId, IEnumerable permissions, param } /// - /// Assigns one permission for a user to many entities + /// Assigns one permission for a user to many entities /// /// /// /// public void AssignPermission(int groupId, char permission, params int[] entityIds) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; - var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; + var sql = + "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; db.Execute(sql, - new - { - groupId, - permission = permission.ToString(CultureInfo.InvariantCulture), - entityIds - }); + new { groupId, permission = permission.ToString(CultureInfo.InvariantCulture), entityIds }); - var actions = entityIds.Select(id => new UserGroup2NodePermissionDto + UserGroup2NodePermissionDto[] actions = entityIds.Select(id => new UserGroup2NodePermissionDto { - NodeId = id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = groupId + NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = groupId }).ToArray(); db.BulkInsertRecords(actions); } /// - /// Assigns one permission to an entity for multiple groups + /// Assigns one permission to an entity for multiple groups /// /// /// /// public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; var groupIdsA = groupIds.ToArray(); - const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; + const string sql = + "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; db.Execute(sql, new { @@ -202,33 +207,31 @@ public void AssignEntityPermission(TEntity entity, char permission, IEnumerable< groupIds = groupIdsA }); - var actions = groupIdsA.Select(id => new UserGroup2NodePermissionDto + UserGroup2NodePermissionDto[] actions = groupIdsA.Select(id => new UserGroup2NodePermissionDto { - NodeId = entity.Id, - Permission = permission.ToString(CultureInfo.InvariantCulture), - UserGroupId = id + NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), UserGroupId = id }).ToArray(); db.BulkInsertRecords(actions); } /// - /// Assigns permissions to an entity for multiple group/permission entries + /// Assigns permissions to an entity for multiple group/permission entries /// /// /// /// - /// This will first clear the permissions for this entity then re-create them + /// This will first clear the permissions for this entity then re-create them /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { - var db = AmbientScope.Database; + IUmbracoDatabase db = AmbientScope.Database; const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; db.Execute(sql, new { nodeId = permissionSet.EntityId }); var toInsert = new List(); - foreach (var entityPermission in permissionSet.PermissionsSet) + foreach (EntityPermission entityPermission in permissionSet.PermissionsSet) { foreach (var permission in entityPermission.AssignedPermissions) { @@ -244,62 +247,21 @@ public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) db.BulkInsertRecords(toInsert); } - #region Not implemented (don't need to for the purposes of this repo) - - protected override ContentPermissionSet PerformGet(int id) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override Sql GetBaseQuery(bool isCount) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override string GetBaseWhereClause() - { - throw new InvalidOperationException("This method won't be implemented."); - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override void PersistDeletedItem(ContentPermissionSet entity) - { - throw new InvalidOperationException("This method won't be implemented."); - } - - #endregion - /// - /// Used to add or update entity permissions during a content item being updated + /// Used to add or update entity permissions during a content item being updated /// /// - protected override void PersistNewItem(ContentPermissionSet entity) - { + protected override void PersistNewItem(ContentPermissionSet entity) => //does the same thing as update PersistUpdatedItem(entity); - } /// - /// Used to add or update entity permissions during a content item being updated + /// Used to add or update entity permissions during a content item being updated /// /// protected override void PersistUpdatedItem(ContentPermissionSet entity) { - var asIEntity = (IEntity) entity; + var asIEntity = (IEntity)entity; if (asIEntity.HasIdentity == false) { throw new InvalidOperationException("Cannot create permissions for an entity without an Id"); @@ -308,14 +270,16 @@ protected override void PersistUpdatedItem(ContentPermissionSet entity) ReplaceEntityPermissions(entity); } - private static EntityPermissionCollection ConvertToPermissionList(IEnumerable result) + private static EntityPermissionCollection ConvertToPermissionList( + IEnumerable result) { var permissions = new EntityPermissionCollection(); - var nodePermissions = result.GroupBy(x => x.NodeId); - foreach (var np in nodePermissions) + IEnumerable> nodePermissions = result.GroupBy(x => x.NodeId); + foreach (IGrouping np in nodePermissions) { - var userGroupPermissions = np.GroupBy(x => x.UserGroupId); - foreach (var permission in userGroupPermissions) + IEnumerable> userGroupPermissions = + np.GroupBy(x => x.UserGroupId); + foreach (IGrouping permission in userGroupPermissions) { var perms = permission.Select(x => x.Permission).Distinct().ToArray(); permissions.Add(new EntityPermission(permission.Key, np.Key, perms)); @@ -324,5 +288,29 @@ private static EntityPermissionCollection ConvertToPermissionList(IEnumerable + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable PerformGetAll(params int[] ids) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable PerformGetByQuery(IQuery query) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override Sql GetBaseQuery(bool isCount) => + throw new InvalidOperationException("This method won't be implemented."); + + protected override string GetBaseWhereClause() => + throw new InvalidOperationException("This method won't be implemented."); + + protected override IEnumerable GetDeleteClauses() => new List(); + + protected override void PersistDeletedItem(ContentPermissionSet entity) => + throw new InvalidOperationException("This method won't be implemented."); + + #endregion } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index f035ffbd9ba1..6ab29aa47e4a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -16,86 +17,183 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { internal class RedirectUrlRepository : EntityRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) + public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, + ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + } - protected override int PerformCount(IQuery query) + public IRedirectUrl Get(string url, Guid contentKey, string culture) { - throw new NotSupportedException("This repository does not support this method."); + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false).Where(x => + x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); + RedirectUrlDto dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public void DeleteAll() => Database.Execute("DELETE FROM umbracoRedirectUrl"); + + public void DeleteContentUrls(Guid contentKey) => + Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); + + public void Delete(Guid id) => Database.Delete(id); + + public IRedirectUrl GetMostRecentUrl(string url) + { + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + RedirectUrlDto dto = dtos.FirstOrDefault(); + return dto == null ? null : Map(dto); + } + + public IRedirectUrl GetMostRecentUrl(string url, string culture) + { + if (string.IsNullOrWhiteSpace(culture)) + { + return GetMostRecentUrl(url); + } + + var urlHash = url.GenerateHash(); + Sql sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash && + (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + RedirectUrlDto dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + + if (dto == null) + { + dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + } + + return dto == null ? null : Map(dto); + } + + public IEnumerable GetContentUrls(Guid contentKey) + { + Sql sql = GetBaseQuery(false) + .Where(x => x.ContentKey == contentKey) + .OrderByDescending(x => x.CreateDateUtc); + List dtos = Database.Fetch(sql); + return dtos.Select(Map); } - protected override bool PerformExists(Guid id) + public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) { - return PerformGet(id) != null; + Sql sql = GetBaseQuery(false) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + return result.Items.Select(Map); } + public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + Sql sql = GetBaseQuery(false) + .Where( + string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), + SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + IEnumerable rules = result.Items.Select(Map); + return rules; + } + + public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + Sql sql = GetBaseQuery(false) + .Where( + string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), + SqlSyntax.GetQuotedColumnName("Url")), + new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) + .OrderByDescending(x => x.CreateDateUtc); + Page result = Database.Page(pageIndex + 1, pageSize, sql); + total = Convert.ToInt32(result.TotalItems); + + IEnumerable rules = result.Items.Select(Map); + return rules; + } + + protected override int PerformCount(IQuery query) => + throw new NotSupportedException("This repository does not support this method."); + + protected override bool PerformExists(Guid id) => PerformGet(id) != null; + protected override IRedirectUrl PerformGet(Guid id) { - var sql = GetBaseQuery(false).Where(x => x.Id == id); - var dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); + Sql sql = GetBaseQuery(false).Where(x => x.Id == id); + RedirectUrlDto dto = Database.Fetch(sql.SelectTop(1)).FirstOrDefault(); return dto == null ? null : Map(dto); } protected override IEnumerable PerformGetAll(params Guid[] ids) { if (ids.Length > Constants.Sql.MaxParameterCount) - throw new NotSupportedException($"This repository does not support more than {Constants.Sql.MaxParameterCount} ids."); + { + throw new NotSupportedException( + $"This repository does not support more than {Constants.Sql.MaxParameterCount} ids."); + } - var sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); - var dtos = Database.Fetch(sql); + Sql sql = GetBaseQuery(false).WhereIn(x => x.Id, ids); + List dtos = Database.Fetch(sql); return dtos.WhereNotNull().Select(Map); } - protected override IEnumerable PerformGetByQuery(IQuery query) - { + protected override IEnumerable PerformGetByQuery(IQuery query) => throw new NotSupportedException("This repository does not support this method."); - } protected override Sql GetBaseQuery(bool isCount) { - var sql = Sql(); + Sql sql = Sql(); if (isCount) + { sql.Select(@"COUNT(*) FROM umbracoRedirectUrl JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + } else + { sql.Select(@"umbracoRedirectUrl.*, umbracoNode.id AS contentId FROM umbracoRedirectUrl JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); + } + return sql; } - protected override string GetBaseWhereClause() - { - return "id = @id"; - } + protected override string GetBaseWhereClause() => "id = @id"; protected override IEnumerable GetDeleteClauses() { - var list = new List - { - "DELETE FROM umbracoRedirectUrl WHERE id = @id" - }; + var list = new List { "DELETE FROM umbracoRedirectUrl WHERE id = @id" }; return list; } protected override void PersistNewItem(IRedirectUrl entity) { - var dto = Map(entity); + RedirectUrlDto dto = Map(entity); Database.Insert(dto); entity.Id = entity.Key.GetHashCode(); } protected override void PersistUpdatedItem(IRedirectUrl entity) { - var dto = Map(entity); + RedirectUrlDto dto = Map(entity); Database.Update(dto); } private static RedirectUrlDto Map(IRedirectUrl redirectUrl) { - if (redirectUrl == null) return null; + if (redirectUrl == null) + { + return null; + } return new RedirectUrlDto { @@ -110,7 +208,10 @@ private static RedirectUrlDto Map(IRedirectUrl redirectUrl) private static IRedirectUrl Map(RedirectUrlDto dto) { - if (dto == null) return null; + if (dto == null) + { + return null; + } var url = new RedirectUrl(); try @@ -130,98 +231,5 @@ private static IRedirectUrl Map(RedirectUrlDto dto) url.EnableChangeTracking(); } } - - public IRedirectUrl Get(string url, Guid contentKey, string culture) - { - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); - var dto = Database.Fetch(sql).FirstOrDefault(); - return dto == null ? null : Map(dto); - } - - public void DeleteAll() - { - Database.Execute("DELETE FROM umbracoRedirectUrl"); - } - - public void DeleteContentUrls(Guid contentKey) - { - Database.Execute("DELETE FROM umbracoRedirectUrl WHERE contentKey=@contentKey", new { contentKey }); - } - - public void Delete(Guid id) - { - Database.Delete(id); - } - - public IRedirectUrl GetMostRecentUrl(string url) - { - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.UrlHash == urlHash) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - var dto = dtos.FirstOrDefault(); - return dto == null ? null : Map(dto); - } - - public IRedirectUrl GetMostRecentUrl(string url, string culture) - { - if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentUrl(url); - var urlHash = url.GenerateHash(); - var sql = GetBaseQuery(false) - .Where(x => x.Url == url && x.UrlHash == urlHash && - (x.Culture == culture.ToLower() || x.Culture == string.Empty)) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - var dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); - - if (dto == null) - dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); - - return dto == null ? null : Map(dto); - } - - public IEnumerable GetContentUrls(Guid contentKey) - { - var sql = GetBaseQuery(false) - .Where(x => x.ContentKey == contentKey) - .OrderByDescending(x => x.CreateDateUtc); - var dtos = Database.Fetch(sql); - return dtos.Select(Map); - } - - public IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - return result.Items.Select(Map); - } - - public IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .Where(string.Format("{0}.{1} LIKE @path", SqlSyntax.GetQuotedTableName("umbracoNode"), SqlSyntax.GetQuotedColumnName("path")), new { path = "%," + rootContentId + ",%" }) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - - var rules = result.Items.Select(Map); - return rules; - } - - public IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total) - { - var sql = GetBaseQuery(false) - .Where(string.Format("{0}.{1} LIKE @url", SqlSyntax.GetQuotedTableName("umbracoRedirectUrl"), SqlSyntax.GetQuotedColumnName("Url")), new { url = "%" + searchTerm.Trim().ToLowerInvariant() + "%" }) - .OrderByDescending(x => x.CreateDateUtc); - var result = Database.Page(pageIndex + 1, pageSize, sql); - total = Convert.ToInt32(result.TotalItems); - - var rules = result.Items.Select(Map); - return rules; - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index 736d183c85b1..919bbeea312d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -21,24 +22,26 @@ internal class TagRepository : EntityRepositoryBase, ITagRepository { public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + } #region Manage Tag Entities /// protected override ITag PerformGet(int id) { - var sql = Sql().Select().From().Where(x => x.Id == id); - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + Sql sql = Sql().Select().From().Where(x => x.Id == id); + TagDto dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : TagFactory.BuildEntity(dto); } /// protected override IEnumerable PerformGetAll(params int[] ids) { - var dtos = ids.Length == 0 + IEnumerable dtos = ids.Length == 0 ? Database.Fetch(Sql().Select().From()) - : Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch => Sql().Select().From().WhereIn(x => x.Id, batch)); + : Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, + batch => Sql().Select().From().WhereIn(x => x.Id, batch)); return dtos.Select(TagFactory.BuildEntity).ToList(); } @@ -46,7 +49,7 @@ protected override IEnumerable PerformGetAll(params int[] ids) /// protected override IEnumerable PerformGetByQuery(IQuery query) { - var sql = Sql().Select().From(); + Sql sql = Sql().Select().From(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); @@ -54,30 +57,21 @@ protected override IEnumerable PerformGetByQuery(IQuery query) } /// - protected override Sql GetBaseQuery(bool isCount) - { - return isCount ? Sql().SelectCount().From() : GetBaseQuery(); - } + protected override Sql GetBaseQuery(bool isCount) => + isCount ? Sql().SelectCount().From() : GetBaseQuery(); - private Sql GetBaseQuery() - { - return Sql().Select().From(); - } + private Sql GetBaseQuery() => Sql().Select().From(); /// - protected override string GetBaseWhereClause() - { - return "id = @id"; - } + protected override string GetBaseWhereClause() => "id = @id"; /// protected override IEnumerable GetDeleteClauses() { var list = new List - { - "DELETE FROM cmsTagRelationship WHERE tagId = @id", - "DELETE FROM cmsTags WHERE id = @id" - }; + { + "DELETE FROM cmsTagRelationship WHERE tagId = @id", "DELETE FROM cmsTags WHERE id = @id" + }; return list; } @@ -86,7 +80,7 @@ protected override void PersistNewItem(ITag entity) { entity.AddingEntity(); - var dto = TagFactory.BuildDto(entity); + TagDto dto = TagFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; @@ -98,7 +92,7 @@ protected override void PersistUpdatedItem(ITag entity) { entity.UpdatingEntity(); - var dto = TagFactory.BuildDto(entity); + TagDto dto = TagFactory.BuildDto(entity); Database.Update(dto); entity.ResetDirtyProperties(); @@ -113,18 +107,21 @@ protected override void PersistUpdatedItem(ITag entity) public void Assign(int contentId, int propertyTypeId, IEnumerable tags, bool replaceTags = true) { // to no-duplicates array - var tagsA = tags.Distinct(new TagComparer()).ToArray(); + ITag[] tagsA = tags.Distinct(new TagComparer()).ToArray(); // replacing = clear all if (replaceTags) { - var sql0 = Sql().Delete().Where(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId); + Sql sql0 = Sql().Delete() + .Where(x => x.NodeId == contentId && x.PropertyTypeId == propertyTypeId); Database.Execute(sql0); } // no tags? nothing else to do if (tagsA.Length == 0) + { return; + } // tags // using some clever logic (?) to insert tags that don't exist in 1 query @@ -163,7 +160,8 @@ public void Remove(int contentId, int propertyTypeId, IEnumerable tags) var tagSetSql = GetTagSet(tags); var group = SqlSyntax.GetQuotedColumnName("group"); - var deleteSql = $@"DELETE FROM cmsTagRelationship WHERE nodeId = {contentId} AND propertyTypeId = {propertyTypeId} AND tagId IN ( + var deleteSql = + $@"DELETE FROM cmsTagRelationship WHERE nodeId = {contentId} AND propertyTypeId = {propertyTypeId} AND tagId IN ( SELECT id FROM cmsTags INNER JOIN {tagSetSql} ON ( tagSet.tag = cmsTags.tag AND tagSet.{group} = cmsTags.{group} AND COALESCE(tagSet.languageId, -1) = COALESCE(cmsTags.languageId, -1) ) @@ -173,18 +171,15 @@ public void Remove(int contentId, int propertyTypeId, IEnumerable tags) } /// - public void RemoveAll(int contentId, int propertyTypeId) - { - Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", - new { nodeId = contentId, propertyTypeId = propertyTypeId }); - } + public void RemoveAll(int contentId, int propertyTypeId) => + Database.Execute( + "DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", + new { nodeId = contentId, propertyTypeId }); /// - public void RemoveAll(int contentId) - { + public void RemoveAll(int contentId) => Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId", new { nodeId = contentId }); - } // this is a clever way to produce an SQL statement like this: // @@ -204,10 +199,16 @@ private string GetTagSet(IEnumerable tags) sql.Append("("); - foreach (var tag in tags) + foreach (ITag tag in tags) { - if (first) first = false; - else sql.Append(" UNION "); + if (first) + { + first = false; + } + else + { + sql.Append(" UNION "); + } sql.Append("SELECT N'"); sql.Append(SqlSyntax.EscapeString(tag.Text)); @@ -217,9 +218,14 @@ private string GetTagSet(IEnumerable tags) sql.Append(group); sql.Append(" , "); if (tag.LanguageId.HasValue) + { sql.Append(tag.LanguageId); + } else + { sql.Append("NULL"); + } + sql.Append(" AS languageId"); } @@ -231,19 +237,17 @@ private string GetTagSet(IEnumerable tags) // used to run Distinct() on tags private class TagComparer : IEqualityComparer { - public bool Equals(ITag x, ITag y) - { - return ReferenceEquals(x, y) // takes care of both being null - || x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId; - } + public bool Equals(ITag x, ITag y) => + ReferenceEquals(x, y) // takes care of both being null + || (x != null && y != null && x.Text == y.Text && x.Group == y.Group && x.LanguageId == y.LanguageId); public int GetHashCode(ITag obj) { unchecked { var h = obj.Text.GetHashCode(); - h = h * 397 ^ obj.Group.GetHashCode(); - h = h * 397 ^ (obj.LanguageId?.GetHashCode() ?? 0); + h = (h * 397) ^ obj.Group.GetHashCode(); + h = (h * 397) ^ (obj.LanguageId?.GetHashCode() ?? 0); return h; } } @@ -273,7 +277,7 @@ private class TaggedEntityDto /// public TaggedEntity GetTaggedEntityByKey(Guid key) { - var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); + Sql sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); sql = sql .Where(dto => dto.UniqueId == key); @@ -284,7 +288,7 @@ public TaggedEntity GetTaggedEntityByKey(Guid key) /// public TaggedEntity GetTaggedEntityById(int id) { - var sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); + Sql sql = GetTaggedEntitiesSql(TaggableObjectTypes.All, "*"); sql = sql .Where(dto => dto.NodeId == id); @@ -293,9 +297,10 @@ public TaggedEntity GetTaggedEntityById(int id) } /// - public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string culture = null) + public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, + string culture = null) { - var sql = GetTaggedEntitiesSql(objectType, culture); + Sql sql = GetTaggedEntitiesSql(objectType, culture); sql = sql .Where(x => x.Group == group); @@ -304,30 +309,37 @@ public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes } /// - public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string group = null, string culture = null) + public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, + string group = null, string culture = null) { - var sql = GetTaggedEntitiesSql(objectType, culture); + Sql sql = GetTaggedEntitiesSql(objectType, culture); sql = sql .Where(dto => dto.Text == tag); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return Map(Database.Fetch(sql)); } private Sql GetTaggedEntitiesSql(TaggableObjectTypes objectType, string culture) { - var sql = Sql() + Sql sql = Sql() .Select(x => Alias(x.NodeId, "NodeId")) - .AndSelect(x => Alias(x.Alias, "PropertyTypeAlias"), x => Alias(x.Id, "PropertyTypeId")) - .AndSelect(x => Alias(x.Id, "TagId"), x => Alias(x.Text, "TagText"), x => Alias(x.Group, "TagGroup"), x => Alias(x.LanguageId, "TagLanguage")) + .AndSelect(x => Alias(x.Alias, "PropertyTypeAlias"), + x => Alias(x.Id, "PropertyTypeId")) + .AndSelect(x => Alias(x.Id, "TagId"), x => Alias(x.Text, "TagText"), + x => Alias(x.Group, "TagGroup"), x => Alias(x.LanguageId, "TagLanguage")) .From() .InnerJoin().On((tag, rel) => tag.Id == rel.TagId) - .InnerJoin().On((rel, content) => rel.NodeId == content.NodeId) - .InnerJoin().On((rel, prop) => rel.PropertyTypeId == prop.Id) + .InnerJoin() + .On((rel, content) => rel.NodeId == content.NodeId) + .InnerJoin() + .On((rel, prop) => rel.PropertyTypeId == prop.Id) .InnerJoin().On((content, node) => content.NodeId == node.NodeId); if (culture == null) @@ -344,16 +356,15 @@ private Sql GetTaggedEntitiesSql(TaggableObjectTypes objectType, st if (objectType != TaggableObjectTypes.All) { - var nodeObjectType = GetNodeObjectType(objectType); + Guid nodeObjectType = GetNodeObjectType(objectType); sql = sql.Where(dto => dto.NodeObjectType == nodeObjectType); } return sql; } - private static IEnumerable Map(IEnumerable dtos) - { - return dtos.GroupBy(x => x.NodeId).Select(dtosForNode => + private static IEnumerable Map(IEnumerable dtos) => + dtos.GroupBy(x => x.NodeId).Select(dtosForNode => { var taggedProperties = dtosForNode.GroupBy(x => x.PropertyTypeId).Select(dtosForProperty => { @@ -368,25 +379,27 @@ private static IEnumerable Map(IEnumerable dtos) return new TaggedEntity(dtosForNode.Key, taggedProperties); }).ToList(); - } /// - public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, string culture = null) + public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null, + string culture = null) { - var sql = GetTagsSql(culture, true); + Sql sql = GetTagsSql(culture, true); AddTagsSqlWhere(sql, culture); if (objectType != TaggableObjectTypes.All) { - var nodeObjectType = GetNodeObjectType(objectType); + Guid nodeObjectType = GetNodeObjectType(objectType); sql = sql .Where(dto => dto.NodeObjectType == nodeObjectType); } if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } sql = sql .GroupBy(x => x.Id, x => x.Text, x => x.Group, x => x.LanguageId); @@ -397,7 +410,7 @@ public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, st /// public IEnumerable GetTagsForEntity(int contentId, string group = null, string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); AddTagsSqlWhere(sql, culture); @@ -405,8 +418,10 @@ public IEnumerable GetTagsForEntity(int contentId, string group = null, st .Where(dto => dto.NodeId == contentId); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } @@ -414,7 +429,7 @@ public IEnumerable GetTagsForEntity(int contentId, string group = null, st /// public IEnumerable GetTagsForEntity(Guid contentId, string group = null, string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); AddTagsSqlWhere(sql, culture); @@ -422,63 +437,76 @@ public IEnumerable GetTagsForEntity(Guid contentId, string group = null, s .Where(dto => dto.UniqueId == contentId); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } /// - public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, string culture = null) + public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null, + string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); sql = sql - .InnerJoin().On((prop, rel) => prop.Id == rel.PropertyTypeId) + .InnerJoin() + .On((prop, rel) => prop.Id == rel.PropertyTypeId) .Where(x => x.NodeId == contentId) .Where(x => x.Alias == propertyTypeAlias); AddTagsSqlWhere(sql, culture); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } /// - public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, string culture = null) + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null, + string culture = null) { - var sql = GetTagsSql(culture); + Sql sql = GetTagsSql(culture); sql = sql - .InnerJoin().On((prop, rel) => prop.Id == rel.PropertyTypeId) + .InnerJoin() + .On((prop, rel) => prop.Id == rel.PropertyTypeId) .Where(dto => dto.UniqueId == contentId) .Where(dto => dto.Alias == propertyTypeAlias); AddTagsSqlWhere(sql, culture); if (group.IsNullOrWhiteSpace() == false) + { sql = sql .Where(dto => dto.Group == group); + } return ExecuteTagsQuery(sql); } private Sql GetTagsSql(string culture, bool withGrouping = false) { - var sql = Sql() + Sql sql = Sql() .Select(); if (withGrouping) + { sql = sql .AndSelectCount("NodeCount"); + } sql = sql .From() .InnerJoin().On((rel, tag) => tag.Id == rel.TagId) - .InnerJoin().On((content, rel) => content.NodeId == rel.NodeId) + .InnerJoin() + .On((content, rel) => content.NodeId == rel.NodeId) .InnerJoin().On((node, content) => node.NodeId == content.NodeId); if (culture != null && culture != "*") @@ -506,21 +534,19 @@ private Sql AddTagsSqlWhere(Sql sql, string culture) return sql; } - private IEnumerable ExecuteTagsQuery(Sql sql) - { - return Database.Fetch(sql).Select(TagFactory.BuildEntity); - } + private IEnumerable ExecuteTagsQuery(Sql sql) => + Database.Fetch(sql).Select(TagFactory.BuildEntity); private Guid GetNodeObjectType(TaggableObjectTypes type) { switch (type) { case TaggableObjectTypes.Content: - return Cms.Core.Constants.ObjectTypes.Document; + return Constants.ObjectTypes.Document; case TaggableObjectTypes.Media: - return Cms.Core.Constants.ObjectTypes.Media; + return Constants.ObjectTypes.Media; case TaggableObjectTypes.Member: - return Cms.Core.Constants.ObjectTypes.Member; + return Constants.ObjectTypes.Member; default: throw new ArgumentOutOfRangeException(nameof(type)); } diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index e0423cc340d3..e68d3f78336d 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -7,8 +7,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Extensions; -using Umbraco.Core.Collections; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index cdb284fdb0a3..c3f36e92cbbb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -204,7 +204,7 @@ public IDictionary SearchAll(string query) var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.AllowedSections.ToArray(); foreach (KeyValuePair searchableTree in _searchableTreeCollection - .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) + .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) { if (allowedSections.Contains(searchableTree.Value.AppAlias)) { @@ -1027,13 +1027,13 @@ private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes e case UmbracoEntityTypes.Macro: case UmbracoEntityTypes.Template: - var template = Services.FileService.GetTemplate(key); + ITemplate template = _fileService.GetTemplate(key); if (template is null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } - return Mapper.Map(template); + return _umbracoMapper.Map(template); default: throw new NotSupportedException("The " + typeof(EntityController) + @@ -1069,13 +1069,13 @@ private ActionResult GetResultForId(int id, UmbracoEntityTypes enti case UmbracoEntityTypes.Macro: case UmbracoEntityTypes.Template: - var template = Services.FileService.GetTemplate(id); + ITemplate template = _fileService.GetTemplate(id); if (template is null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } - return Mapper.Map(template); + return _umbracoMapper.Map(template); default: throw new NotSupportedException("The " + typeof(EntityController) + @@ -1447,7 +1447,7 @@ private IEnumerable GetAllDictionaryItems() var list = new List(); foreach (IDictionaryItem dictionaryItem in _localizationService.GetRootDictionaryItems() - .OrderBy(DictionaryItemSort())) + .OrderBy(DictionaryItemSort())) { EntityBasic item = _umbracoMapper.Map(dictionaryItem); list.Add(item); @@ -1462,7 +1462,7 @@ private IEnumerable GetAllDictionaryItems() private void GetChildItemsForList(IDictionaryItem dictionaryItem, ICollection list) { foreach (IDictionaryItem childItem in _localizationService.GetDictionaryItemChildren(dictionaryItem.Key) - .OrderBy(DictionaryItemSort())) + .OrderBy(DictionaryItemSort())) { EntityBasic item = _umbracoMapper.Map(childItem); list.Add(item); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index b282b469a36d..a9b706a67761 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -8715,9 +8715,9 @@ "dev": true }, "nouislider": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.4.0.tgz", - "integrity": "sha512-AV7UMhGhZ4Mj6ToMT812Ib8OJ4tAXR2/Um7C4l4ZvvsqujF0WpQTpqqHJ+9xt4174R7ueQOUrBR4yakJpAIPCA==" + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.5.0.tgz", + "integrity": "sha512-p0Rn0a4XzrBJ+JZRhNDYpRYr6sDPkajsjbvEQoTp/AZlNI3NirO15s1t11D25Gk3zVyvNJAzc1DO48cq/KX5Sw==" }, "now-and-later": { "version": "2.0.1", diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs index 4caf7bd00536..c20a5a3acaa4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/StackQueueTests.cs @@ -1,5 +1,5 @@ using NUnit.Framework; -using Umbraco.Core.Collections; +using Umbraco.Cms.Core.Collections; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Collections { @@ -10,7 +10,7 @@ public class StackQueueTests public void Queue() { var sq = new StackQueue(); - for (int i = 0; i < 3; i++) + for (var i = 0; i < 3; i++) { sq.Enqueue(i); } @@ -28,7 +28,7 @@ public void Queue() public void Stack() { var sq = new StackQueue(); - for (int i = 0; i < 3; i++) + for (var i = 0; i < 3; i++) { sq.Push(i); } @@ -46,7 +46,7 @@ public void Stack() public void Stack_And_Queue() { var sq = new StackQueue(); - for (int i = 0; i < 5; i++) + for (var i = 0; i < 5; i++) { if (i % 2 == 0) {