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.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs
new file mode 100644
index 000000000000..b57861c92ac8
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Sql.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Cms.Core
+{
+ public static partial class Constants
+ {
+ public static class Sql
+ {
+ ///
+ /// The maximum amount of parameters that can be used in a query.
+ ///
+ ///
+ /// The actual limit is 2100
+ /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server),
+ /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches.
+ ///
+ public const int MaxParameterCount = 2000;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
index 238065718002..e4d101ff062d 100644
--- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
@@ -251,7 +251,7 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte
// the entity service due to too many Sql parameters.
var list = new List();
- foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
+ foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount))
list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
contentEntities = list.ToArray();
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 734ed2261d18..8ed2205f5950 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -1,61 +1,61 @@
-
- netstandard2.0
- Umbraco.Cms.Core
- Umbraco CMS
- Umbraco.Cms.Core
- Umbraco CMS Core
- Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
- Umbraco CMS
-
+
+ netstandard2.0
+ Umbraco.Cms.Core
+ Umbraco CMS
+ Umbraco.Cms.Core
+ Umbraco CMS Core
+ Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
+ Umbraco CMS
+
-
- bin\Release\Umbraco.Core.xml
-
+
+ bin\Release\Umbraco.Core.xml
+
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
- all
-
-
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
+
+
-
-
- <_Parameter1>Umbraco.Tests
-
-
- <_Parameter1>Umbraco.Tests.Common
-
-
- <_Parameter1>Umbraco.Tests.UnitTests
-
-
- <_Parameter1>Umbraco.Tests.Benchmarks
-
-
- <_Parameter1>Umbraco.Tests.Integration
-
-
- <_Parameter1>DynamicProxyGenAssembly2
-
-
+
+
+ <_Parameter1>Umbraco.Tests
+
+
+ <_Parameter1>Umbraco.Tests.Common
+
+
+ <_Parameter1>Umbraco.Tests.UnitTests
+
+
+ <_Parameter1>Umbraco.Tests.Benchmarks
+
+
+ <_Parameter1>Umbraco.Tests.Integration
+
+
+ <_Parameter1>DynamicProxyGenAssembly2
+
+
-
-
-
+
+
+
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 16c411c772a5..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(2000))
+ 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/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 239f0a89ed71..fd4d1c33b9ca 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -657,7 +657,7 @@ protected IDictionary GetPropertyCollections(List(versions, 2000, batch =>
+ var allPropertyDataDtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select()
.From()
@@ -666,7 +666,7 @@ protected IDictionary GetPropertyCollections(List x.PropertyTypeId).Distinct().ToList();
- var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, 2000, batch =>
+ var allPropertyTypeDtos = Database.FetchByGroups(allPropertyTypeIds, Constants.Sql.MaxParameterCount, batch =>
SqlContext.Sql()
.Select(r => r.Select(x => x.DataTypeDto))
.From()
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index d93c2c832280..d7be081fe144 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -795,7 +795,7 @@ private void CopyTagData(int? sourceLanguageId, int? targetLanguageId, IReadOnly
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
// delete existing relations (for target language)
@@ -933,7 +933,7 @@ private void CopyPropertyData(int? sourceLanguageId, int? targetLanguageId, IRea
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
//
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
//first clear out any existing property data that might already exists under the target language
@@ -1032,7 +1032,7 @@ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection propertyTyp
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
- if (whereInArgsCount > 2000)
+ if (whereInArgsCount > Constants.Sql.MaxParameterCount)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
@@ -1121,14 +1121,20 @@ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection propertyTyp
}
}
- //lookup all matching rows in umbracoDocumentCultureVariation
- var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
- .SelectMany(_ => Database.Fetch(
- Sql().Select().From()
- .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
- .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
- //convert to dictionary with the same key type
- .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
+ // lookup all matching rows in umbracoDocumentCultureVariation
+ // fetch in batches to account for maximum parameter count (distinct languages can't exceed 2000)
+ var languageIds = editedLanguageVersions.Keys.Select(x => x.langId).Distinct().ToArray();
+ var nodeIds = editedLanguageVersions.Keys.Select(x => x.nodeId).Distinct();
+ var docCultureVariationsToUpdate = nodeIds.InGroupsOf(Constants.Sql.MaxParameterCount - languageIds.Length)
+ .SelectMany(group =>
+ {
+ var sql = Sql().Select().From()
+ .WhereIn(x => x.LanguageId, languageIds)
+ .WhereIn(x => x.NodeId, group);
+
+ return Database.Fetch(sql);
+ })
+ .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); //convert to dictionary with the same key type
var toUpdate = new List();
foreach (var ev in editedLanguageVersions)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
index 0ec31d843fe3..bc9892b1ee0e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -263,13 +263,12 @@ public IEnumerable GetDictionaryItemDescendants(Guid? parentId)
Func>> getItemsFromParents = guids =>
{
- //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used.
- return guids.InGroupsOf(2000)
- .Select(@group =>
+ return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
+ .Select(group =>
{
var sqlClause = GetBaseQuery(false)
.Where(x => x.Parent != null)
- .Where($"{SqlSyntax.GetQuotedColumnName("parent")} IN (@parentIds)", new { parentIds = @group });
+ .WhereIn(x => x.Parent, group);
var translator = new SqlTranslator(sqlClause, Query());
var sql = translator.Translate();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 4c9b19f1a91f..75d66cf09d7f 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -1390,7 +1390,7 @@ private IDictionary GetContentSchedule(params in
{
var result = new Dictionary();
- var scheduleDtos = Database.FetchByGroups(contentIds, 2000, batch => Sql()
+ var scheduleDtos = Database.FetchByGroups(contentIds, Constants.Sql.MaxParameterCount, batch => Sql()
.Select()
.From()
.WhereIn(x => x.NodeId, batch));
@@ -1440,7 +1440,7 @@ private IDictionary> GetContentVariations(List>();
- var dtos = Database.FetchByGroups(versions, 2000, batch
+ var dtos = Database.FetchByGroups(versions, Constants.Sql.MaxParameterCount, batch
=> Sql()
.Select()
.From()
@@ -1469,7 +1469,7 @@ private IDictionary> GetDocumentVariations(List<
{
var ids = temps.Select(x => x.Id);
- var dtos = Database.FetchByGroups(ids, 2000, batch =>
+ var dtos = Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, batch =>
Sql()
.Select()
.From()
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
index bc1b1b1881c8..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);
}
@@ -62,39 +71,39 @@ protected override IEnumerable PerformGetAll(params int[] ids)
{
if (ids.Any())
{
- return Database.FetchByGroups(ids, 2000, batch =>
- GetBaseQuery(false)
- .Where(x => x.NodeObjectType == NodeObjectTypeId)
- .WhereIn(x => x.NodeId, batch))
+ return Database.FetchByGroups(ids, Constants.Sql.MaxParameterCount, 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/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
index 4eb4f108ce80..f1b9c77d0aa8 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
@@ -279,7 +279,7 @@ private IEnumerable BuildVariants(IEnumerable(v.Select(x => x.Id), 2000, GetVariantInfos);
+ var dtos = Database.FetchByGroups(v.Select(x => x.Id), Constants.Sql.MaxParameterCount, GetVariantInfos);
// group by node id (each group contains all languages)
var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
index 25927213e542..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)
{
@@ -209,38 +153,94 @@ public IEnumerable GetMany(params TId[] ids)
// 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
- const int maxParams = 2000;
- if (ids.Length <= maxParams)
+ if (ids.Length <= Constants.Sql.MaxParameterCount)
{
return CachePolicy.GetAll(ids, PerformGetAll);
}
var entities = new List();
- foreach (var groupOfIds in ids.InGroupsOf(maxParams))
+ foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
+ entities.AddRange(CachePolicy.GetAll(group.ToArray(), PerformGetAll));
}
return entities;
}
///
- /// 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 22083eae30f3..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