diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs
index 9bf9c365f0..1d8b19f889 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 443032c67a..f07867cccc 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 @@ namespace Umbraco.Extensions
/// 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 @@ namespace Umbraco.Extensions
// 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 @@ namespace Umbraco.Extensions
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 @@ namespace Umbraco.Extensions
}
///
- /// 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 c439c1a80d..e3685dd32c 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.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Querying;
@@ -16,26 +17,47 @@ using Umbraco.Extensions;
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
if (ids.Length == 0)
{
- var sql = Sql()
+ Sql sql = Sql()
.Select()
.From();
@@ -53,9 +75,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
///
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 ac69d02587..b30c5ae1a4 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.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Querying;
@@ -14,47 +15,55 @@ using Umbraco.Extensions;
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
containerObjectType = entity.ContainerObjectType
}));
- foreach (var childDto in childDtos)
+ foreach (NodeDto childDto in childDtos)
{
childDto.ParentId = nodeDto.ParentId;
Database.Update(childDto);
@@ -155,31 +175,51 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
// 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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
//
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 1589c9d52e..79e6f732a2 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.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Persistence;
@@ -14,38 +15,37 @@ using Umbraco.Extensions;
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
///
- /// 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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
///
- /// Get the entity id for the
+ /// Adds or Updates an entity of type TEntity
///
- protected virtual TId GetEntityId(TEntity entity)
- => (TId)(object)entity.Id;
-
- ///
- /// Create the repository cache policy
- ///
- 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,11 +127,77 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
///
- /// Deletes the passed in entity
+ /// Deletes the passed in entity
///
public virtual void Delete(TEntity entity)
=> CachePolicy.Delete(entity, PersistDeletedItem);
+ ///
+ /// 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
+ ///
+ public IEnumerable GetMany(params TId[] ids)
+ {
+ // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
+ ids = ids.Distinct()
+
+ // don't query by anything that is a default of T (like a zero)
+ // TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids
+ // .Where(x => Equals(x, default(TId)) == false)
+ .ToArray();
+
+ // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
+ // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
+ if (ids.Length <= Constants.Sql.MaxParameterCount)
+ {
+ return CachePolicy.GetAll(ids, PerformGetAll);
+ }
+
+ var entities = new List();
+ foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
+ {
+ entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll));
+ }
+
+ return entities;
+ }
+
+ ///
+ /// 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
+ ///
+ public bool Exists(TId id)
+ => CachePolicy.Exists(id, PerformExists, PerformGetAll);
+
+ ///
+ /// 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);
@@ -162,24 +217,24 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
protected virtual bool PerformExists(TId id)
{
- var sql = GetBaseQuery(true);
- sql.Where(GetBaseWhereClause(), new { id = id });
+ Sql sql = GetBaseQuery(true);
+ sql.Where(GetBaseWhereClause(), new { id });
var count = Database.ExecuteScalar(sql);
return count == 1;
}
protected virtual int PerformCount(IQuery query)
{
- var sqlClause = GetBaseQuery(true);
+ Sql sqlClause = GetBaseQuery(true);
var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate();
+ Sql sql = translator.Translate();
return Database.ExecuteScalar(sql);
}
protected virtual void PersistDeletedItem(TEntity entity)
{
- var deletes = GetDeleteClauses();
+ IEnumerable deletes = GetDeleteClauses();
foreach (var delete in deletes)
{
Database.Execute(delete, new { id = GetEntityId(entity) });
@@ -187,59 +242,5 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
entity.DeleteDate = DateTime.Now;
}
-
- ///
- /// 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
- ///
- public IEnumerable GetMany(params TId[] ids)
- {
- // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
- ids = ids.Distinct()
-
- // don't query by anything that is a default of T (like a zero)
- // TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids
- // .Where(x => Equals(x, default(TId)) == false)
- .ToArray();
-
- // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
- // the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
- if (ids.Length <= Constants.Sql.MaxParameterCount)
- {
- return CachePolicy.GetAll(ids, PerformGetAll);
- }
-
- var entities = new List();
- foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
- {
- entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll));
- }
-
- return entities;
- }
-
- ///
- /// 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
- ///
- public bool Exists(TId id)
- => CachePolicy.Exists(id, PerformExists, PerformGetAll);
-
- ///
- /// Returns an integer with the count of entities found with the passed in query
- ///
- public int Count(IQuery query)
- => PerformCount(query);
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
index 6485dc286a..4031971ddc 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
@@ -4,6 +4,7 @@ using System.Linq;
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 @@ using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics;
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
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