diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index fea0a61589..f7341d112b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -67,7 +68,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets paged content items. /// + /// Here, can be null but cannot. IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); + IQuery filter, Ordering ordering); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 2e0139aa30..4e68262e55 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -232,16 +232,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - public abstract IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); - - // sql: the main sql - // filterSql: a filtering ? fixme different from v7? - // orderBy: the name of an ordering field - // orderDirection: direction for orderBy - // orderBySystemField: whether orderBy is a system field or a custom field (property value) - private Sql PrepareSqlForPage(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField) + private Sql PreparePageSql(Sql sql, Sql filterSql, Ordering ordering) { - if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; + // non-filtering, non-ordering = nothing to do + if (filterSql == null && ordering.IsEmpty) return sql; // preserve original var psql = new Sql(sql.SqlContext, sql.SQL, sql.Arguments); @@ -251,25 +245,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement psql.Append(filterSql); // non-sorting, we're done - if (string.IsNullOrEmpty(orderBy)) + if (ordering.IsEmpty) return psql; - // else apply sort - var dbfield = orderBySystemField - ? GetOrderBySystemField(ref psql, orderBy) - : GetOrderByNonSystemField(ref psql, orderBy); - - if (orderDirection == Direction.Ascending) - psql.OrderBy(dbfield); - else - psql.OrderByDescending(dbfield); + // else apply ordering + ApplyOrdering(ref psql, ordering); // no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - dbfield = GetDatabaseFieldNameForOrderBy("umbracoNode", "id"); - if (orderBySystemField == false || orderBy.InvariantEquals(dbfield) == false) + var dbfield = GetQuotedFieldName("umbracoNode", "id"); + if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { // get alias, if aliased var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); @@ -282,43 +269,92 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines - psql = new Sql(psql.SqlContext, psql.SQL.ToSingleLine(), psql.Arguments); + psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); return psql; } - private string GetOrderBySystemField(ref Sql sql, string orderBy) + private void ApplyOrdering(ref Sql sql, Ordering ordering) { - // get the database field eg "[table].[column]" - var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); + if (sql == null) throw new ArgumentNullException(nameof(sql)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - // for SqlServer pagination to work, the "order by" field needs to be the alias eg if - // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs - // to be "NodeDto__Text" and NOT "umbracoNode.text". - // not sure about SqlCE nor MySql, so better do it too. initially thought about patching - // NPoco but that would be expensive and not 100% possible, so better give NPoco proper - // queries to begin with. - // thought about maintaining a map of columns-to-aliases in the sql context but that would - // be expensive and most of the time, useless. so instead we parse the SQL looking for the - // alias. somewhat expensive too but nothing's free. + var orderBy = ordering.IsCustomField + ? ApplyCustomOrdering(ref sql, ordering) + : ApplySystemOrdering(ref sql, ordering); - // note: ContentTypeAlias is not properly managed because it's not part of the query to begin with! - - // get alias, if aliased - var matches = SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL); - var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(dbfield)); - if (match != null) dbfield = match.Groups[2].Value; - - return dbfield; + if (ordering.Direction == Direction.Ascending) + sql.OrderBy(orderBy); + else + sql.OrderByDescending(orderBy); } - private string GetOrderByNonSystemField(ref Sql sql, string orderBy) + protected virtual string ApplySystemOrdering(ref Sql sql, Ordering ordering) + { + // id is invariant + if (ordering.OrderBy.InvariantEquals("id")) + return SqlSyntax.GetFieldName(x => x.NodeId); + + // sort order is invariant + if (ordering.OrderBy.InvariantEquals("sortOrder")) + return SqlSyntax.GetFieldName(x => x.SortOrder); + + // path is invariant + if (ordering.OrderBy.InvariantEquals("path")) + return SqlSyntax.GetFieldName(x => x.Path); + + // note: 'owner' is the user who created the item as a whole, + // we don't have an 'owner' per culture (should we?) + if (ordering.OrderBy.InvariantEquals("owner")) + { + var joins = Sql() + .InnerJoin("ownerUser").On((node, user) => node.UserId == user.Id, aliasRight: "ownerUser"); + + sql = InsertJoins(sql, joins); + + return SqlSyntax.GetFieldName(x => x.UserName, "ownerUser"); + } + + // note: each version culture variation has a date too, + // maybe we would want to use it instead? + if (ordering.OrderBy.InvariantEquals("versionDate") || ordering.OrderBy.InvariantEquals("updateDate")) + return SqlSyntax.GetFieldName(x => x.VersionDate); + + // create date is invariant (we don't keep each culture's creation date) + if (ordering.OrderBy.InvariantEquals("createDate")) + return SqlSyntax.GetFieldName(x => x.CreateDate); + + // name is variant + if (ordering.OrderBy.InvariantEquals("name")) + { + // no culture = can only work on the invariant name + if (ordering.Culture.IsNullOrWhiteSpace()) + return SqlSyntax.GetFieldName(x => x.Text); + + // culture = must work on variant name ?? invariant name + // insert proper join and return coalesced ordering field + + var joins = Sql() + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + + sql = InsertJoins(sql, joins); + + return SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; + } + + // previously, we'd accept anything and just sanitize it - not anymore + throw new NotSupportedException($"Ordering by {ordering.OrderBy} not supported."); + } + + private string ApplyCustomOrdering(ref Sql sql, Ordering ordering) { // sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through value // from 'current' content version for the given order by field var sortedInt = string.Format(SqlContext.SqlSyntax.ConvertIntegerToOrderableString, "intValue"); + var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue"); var sortedDate = string.Format(SqlContext.SqlSyntax.ConvertDateToOrderableString, "dateValue"); var sortedString = "COALESCE(varcharValue,'')"; // assuming COALESCE is ok for all syntaxes - var sortedDecimal = string.Format(SqlContext.SqlSyntax.ConvertDecimalToOrderableString, "decimalValue"); // needs to be an outer join since there's no guarantee that any of the nodes have values for this property var innerSql = Sql().Select($@"CASE @@ -329,49 +365,59 @@ namespace Umbraco.Core.Persistence.Repositories.Implement END AS customPropVal, cver.nodeId AS customPropNodeId") .From("cver") - .InnerJoin("opdata").On((left, right) => left.Id == right.Id, "cver", "opdata") - .InnerJoin("optype").On((left, right) => left.PropertyTypeId == right.Id, "opdata", "optype") + .InnerJoin("opdata") + .On((version, pdata) => version.Id == pdata.VersionId, "cver", "opdata") + .InnerJoin("optype").On((pdata, ptype) => pdata.PropertyTypeId == ptype.Id, "opdata", "optype") + .LeftJoin().On((pdata, lang) => pdata.LanguageId == lang.Id, "opdata") .Where(x => x.Current, "cver") // always query on current (edit) values - .Where(x => x.Alias == "", "optype"); + .Where(x => x.Alias == ordering.OrderBy, "optype") + .Where((opdata, lang) => opdata.LanguageId == null || lang.IsoCode == ordering.Culture, "opdata"); - // @0 is for x.Current ie 'true' = 1 - // @1 is for x.Alias - var innerSqlString = innerSql.SQL.Replace("@0", "1").Replace("@1", "@" + sql.Arguments.Length); + // merge arguments + var argsList = sql.Arguments.ToList(); + var innerSqlString = ParameterHelper.ProcessParams(innerSql.SQL, innerSql.Arguments, argsList); + + // create the outer join complete sql fragment var outerJoinTempTable = $@"LEFT OUTER JOIN ({innerSqlString}) AS customPropData ON customPropData.customPropNodeId = {Constants.DatabaseSchema.Tables.Node}.id "; // trailing space is important! - // insert this just above the last WHERE - var pos = sql.SQL.InvariantIndexOf("WHERE"); - if (pos < 0) throw new Exception("Oops, WHERE not found."); - var newSql = sql.SQL.Insert(pos, outerJoinTempTable); + // insert this just above the first WHERE + var newSql = InsertBefore(sql.SQL, "WHERE", outerJoinTempTable); - var newArgs = sql.Arguments.ToList(); - newArgs.Add(orderBy); + // insert the SQL selected field, too, before the first FROM else ordering cannot work + newSql = InsertBefore(newSql, "FROM", ", customPropData.customPropVal "); // trailing space is important! - // insert the SQL selected field, too, else ordering cannot work - if (sql.SQL.StartsWith("SELECT ") == false) throw new Exception("Oops: SELECT not found."); - newSql = newSql.Insert("SELECT ".Length, "customPropData.customPropVal, "); - - sql = new Sql(sql.SqlContext, newSql, newArgs.ToArray()); + // create the new sql + sql = Sql(newSql, argsList.ToArray()); // and order by the custom field + // this original code means that an ascending sort would first expose all NULL values, ie items without a value return "customPropData.customPropVal"; + // this better (?) code ensures that items without a value always come last (ie both in asc- and desc-ending sorts) + // not enabled yet - should we? + //return "(CASE WHEN customPropData.customPropVal IS NULL THEN 1 ELSE 0 END), customPropData.customPropVal"; } + public abstract IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering); + + // here, filter can be null and ordering cannot protected IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, Func, IEnumerable> mapDtos, - string orderBy, Direction orderDirection, bool orderBySystemField, - Sql filterSql = null) // fixme filter is different on v7? + Sql filter, + Ordering ordering) { - if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); // start with base query, and apply the supplied IQuery - if (query == null) query = AmbientScope.SqlContext.Query(); + if (query == null) query = Query(); var sql = new SqlTranslator(GetBaseQuery(QueryType.Many), query).Translate(); // sort and filter - sql = PrepareSqlForPage(sql, filterSql, orderBy, orderDirection, orderBySystemField); + sql = PreparePageSql(sql, filter, ordering); // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); @@ -498,36 +544,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return result; } - protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) + protected string InsertBefore(string s, string atToken, string insert) { - // translate the supplied "order by" field, which were originally defined for in-memory - // object sorting of ContentItemBasic instance, to the actual database field names. - - switch (orderBy.ToUpperInvariant()) - { - case "VERSIONDATE": - case "UPDATEDATE": - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "versionDate"); - case "CREATEDATE": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "createDate"); - case "NAME": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "text"); - case "PUBLISHED": - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published"); - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return GetDatabaseFieldNameForOrderBy("umbracoNode", "nodeUser"); - case "PATH": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "path"); - case "SORTORDER": - return GetDatabaseFieldNameForOrderBy("umbracoNode", "sortOrder"); - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } + var pos = s.InvariantIndexOf(atToken); + if (pos < 0) throw new Exception($"Could not find token \"{atToken}\"."); + return s.Insert(pos, insert); } - protected string GetDatabaseFieldNameForOrderBy(string tableName, string fieldName) + protected Sql InsertJoins(Sql sql, Sql joins) + { + var joinsSql = joins.SQL; + var args = sql.Arguments; + + // merge args if any + if (joins.Arguments.Length > 0) + { + var argsList = args.ToList(); + joinsSql = ParameterHelper.ProcessParams(joinsSql, joins.Arguments, argsList); + args = argsList.ToArray(); + } + + return Sql(InsertBefore(sql.SQL, "WHERE", joinsSql), args); + } + + protected string GetQuotedFieldName(string tableName, string fieldName) { return SqlContext.SqlSyntax.GetQuotedTableName(tableName) + "." + SqlContext.SqlSyntax.GetQuotedColumnName(fieldName); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 28c0b0dec6..60aa155dc0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement @@ -673,13 +674,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement PermissionRepository.Save(permission); } - /// - /// Gets paged content results. - /// + /// public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, - IQuery filter = null) + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) { Sql filterSql = null; @@ -692,8 +690,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetPage(query, pageIndex, pageSize, out totalRecords, x => MapDtosToContent(x), - orderBy, orderDirection, orderBySystemField, - filterSql); + filterSql, + ordering); } public bool IsPathPublished(IContent content) @@ -814,25 +812,51 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { - // NOTE see sortby.prevalues.controller.js for possible values - // that need to be handled here or in VersionableRepositoryBase - - //Some custom ones - switch (orderBy.ToUpperInvariant()) + // note: 'updater' is the user who created the latest draft version, + // we don't have an 'updater' per culture (should we?) + if (ordering.OrderBy.InvariantEquals("updater")) { - case "UPDATER": - // fixme orders by id not letter = bad - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.ContentVersion, "userId"); - case "PUBLISHED": - // fixme kill - return GetDatabaseFieldNameForOrderBy(Constants.DatabaseSchema.Tables.Document, "published"); - case "CONTENTTYPEALIAS": - throw new NotSupportedException("Don't know how to support ContentTypeAlias."); + var joins = Sql() + .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser"); + + sql = InsertJoins(sql, joins); + + return SqlSyntax.GetFieldName(x => x.UserName, "updaterUser"); } - return base.GetDatabaseFieldNameForOrderBy(orderBy); + if (ordering.OrderBy.InvariantEquals("published")) + { + // no culture = can only work on the global 'published' flag + if (ordering.Culture.IsNullOrWhiteSpace()) + return "(CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END)"; + + // invariant: left join will yield NULL and we must use pcv to determine published + // variant: left join may yield NULL or something, and that determines published + + var joins = Sql() + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") + .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version + + sql = InsertJoins(sql, joins); + + // insert the SQL selected field, too, before the first FROM else ordering cannot work + // also: must insert the whole CASE body here, and only return 'opub', else NPoco paging code fails + var sqlText = InsertBefore(sql.SQL, "FROM", + + // when invariant, ie 'variations' does not have the culture flag (value 1), use the global 'published' flag on pcv.id, + // otherwise check if there's a version culture variation for the lang, via ccv.id + ", (CASE WHEN (ctype.variations & 1) = 0 THEN (CASE WHEN pcv.id IS NULL THEN 0 ELSE 1 END) ELSE (CASE WHEN ccv.id IS NULL THEN 0 ELSE 1 END) END) AS opub "); // trailing space is important! + + sql = Sql(sqlText, sql.Arguments); + + return "opub"; + } + + return base.ApplySystemOrdering(ref sql, ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 982a5bb885..f289547b10 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -455,11 +456,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - /// - /// Gets paged media results. - /// - public override IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) + /// + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) { Sql filterSql = null; @@ -471,8 +471,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, - filterSql); + x => MapDtosToContent(x), + filterSql, + ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 98c38603b1..ee6727a32e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -492,8 +493,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Gets paged member results. /// - public override IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) + public override IEnumerable GetPage(IQuery query, + long pageIndex, int pageSize, out long totalRecords, + IQuery filter, + Ordering ordering) { Sql filterSql = null; @@ -505,8 +508,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), orderBy, orderDirection, orderBySystemField, - filterSql); + x => MapDtosToContent(x), + filterSql, + ordering); } private string _pagedResultsByQueryWhere; @@ -523,20 +527,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return _pagedResultsByQueryWhere; } - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "EMAIL": - return GetDatabaseFieldNameForOrderBy("cmsMember", "email"); - case "LOGINNAME": - return GetDatabaseFieldNameForOrderBy("cmsMember", "loginName"); - case "USERNAME": - return GetDatabaseFieldNameForOrderBy("cmsMember", "loginName"); - } + if (ordering.OrderBy.InvariantEquals("email")) + return SqlSyntax.GetFieldName(x => x.Email); - return base.GetDatabaseFieldNameForOrderBy(orderBy); + if (ordering.OrderBy.InvariantEquals("loginName")) + return SqlSyntax.GetFieldName(x => x.LoginName); + + if (ordering.OrderBy.InvariantEquals("userName")) + return SqlSyntax.GetFieldName(x => x.LoginName); + + return base.ApplySystemOrdering(ref sql, ordering); } private IEnumerable MapDtosToContent(List dtos, bool withCache = false) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 2e788382fe..022bee8b41 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -166,31 +166,28 @@ namespace Umbraco.Core.Services IEnumerable GetContentInRecycleBin(); /// - /// Gets child documents of a given parent. + /// Gets child documents of a parent. /// /// The parent identifier. /// The page number. /// The page size. /// Total number of documents. - /// A field to order by. - /// The ordering direction. /// Search text filter. + /// Ordering infos. IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string filter = null, Ordering ordering = null); /// - /// Gets child documents of a given parent. + /// Gets child documents of a parent. /// /// The parent identifier. /// The page number. /// The page size. /// Total number of documents. - /// A field to order by. - /// The ordering direction. - /// A flag indicating whether the ordering field is a system field. /// Query filter. + /// Ordering infos. IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + IQuery filter, Ordering ordering = null); /// /// Gets descendant documents of a given parent. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index da59615126..18dcf6beac 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -553,97 +553,47 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string filter = null, Ordering ordering = null) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : Query().Where(x => x.Name.Contains(filter)); - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); - } + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, filterQuery, ordering); } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// An Enumerable list of objects + /// public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + IQuery filter, Ordering ordering = null) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (ordering == null) + ordering = Ordering.By("sortOrder"); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); - var query = Query(); - //if the id is System Root, then just get all - NO! does not make sense! - //if (id != Constants.System.Root) - query.Where(x => x.ParentId == id); - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var query = Query().Where(x => x.ParentId == id); + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); - var filterQuery = filter.IsNullOrWhiteSpace() - ? null - : Query().Where(x => x.Name.Contains(filter)); + var filterQuery = filter.IsNullOrWhiteSpace() + ? null + : Query().Where(x => x.Name.Contains(filter)); - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); - } + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery); } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search filter - /// An Enumerable list of objects + /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); @@ -654,6 +604,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.ContentTree); var query = Query(); + //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -665,7 +616,8 @@ namespace Umbraco.Core.Services.Implement } query.Where(x => x.Path.SqlStartsWith($"{contentPath[0].Path},", TextColumnType.NVarchar)); } - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 8f6a1c6000..431e20044c 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -515,14 +515,9 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); - var query = Query(); - //if the id is System Root, then just get all - NO! does not make sense! - //if (id != Constants.System.Root) - - query.Where(x => x.ParentId == id); - - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + var query = Query().Where(x => x.ParentId == id); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -561,7 +556,7 @@ namespace Umbraco.Core.Services.Implement var filterQuery = filter.IsNullOrWhiteSpace() ? null : Query().Where(x => x.Name.Contains(filter)); - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filterQuery, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -607,6 +602,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.MediaTree); var query = Query(); + //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -618,7 +614,8 @@ namespace Umbraco.Core.Services.Implement } query.Where(x => x.Path.SqlStartsWith(mediaPath[0].Path + ",", TextColumnType.NVarchar)); } - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 288809bf33..211e30d01c 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -387,7 +387,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MemberTree); - return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } @@ -405,7 +405,7 @@ namespace Umbraco.Core.Services.Implement scope.ReadLock(Constants.Locks.MemberTree); var query1 = memberTypeAlias == null ? null : Query().Where(x => x.ContentTypeAlias == memberTypeAlias); var query2 = filter == null ? null : Query().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter) || x.Email.Contains(filter)); - return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, query2); + return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -557,7 +557,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name")); } } @@ -598,7 +598,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email")); } } @@ -639,7 +639,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } diff --git a/src/Umbraco.Core/Services/Ordering.cs b/src/Umbraco.Core/Services/Ordering.cs new file mode 100644 index 0000000000..9713aa6bf5 --- /dev/null +++ b/src/Umbraco.Core/Services/Ordering.cs @@ -0,0 +1,81 @@ +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Services +{ + /// + /// Represents ordering information. + /// + public class Ordering + { + private static readonly Ordering DefaultOrdering = new Ordering(null); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. + /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. + /// + public Ordering(string orderBy, Direction direction = Direction.Ascending, string culture = null, bool isCustomField = false) + { + OrderBy = orderBy.IfNullOrWhiteSpace(null); // empty is null and means, not sorting + Direction = direction; + Culture = culture.IfNullOrWhiteSpace(string.Empty); // empty is "" and means, invariant + IsCustomField = isCustomField; + } + + /// + /// Creates a new instance of the class. + /// + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. + /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. + /// + public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string culture = null, bool isCustomField = false) + => new Ordering(orderBy, direction, culture, isCustomField); + + /// + /// Gets the default instance. + /// + public static Ordering ByDefault() + => DefaultOrdering; + + /// + /// Gets the name of the ordering field. + /// + public string OrderBy { get; } + + /// + /// Gets the ordering direction. + /// + public Direction Direction { get; } + + /// + /// Gets (ISO) culture to consider when sorting multi-lingual fields. + /// + public string Culture { get; } + + /// + /// Gets a value indicating whether the ordering field is a custom user property. + /// + public bool IsCustomField { get; } + + /// + /// Gets a value indicating whether this ordering is the default ordering. + /// + public bool IsEmpty => this == DefaultOrdering || OrderBy == null; + + /// + /// Gets a value indicating whether the culture of this ordering is invariant. + /// + public bool IsInvariant => this == DefaultOrdering || Culture == string.Empty; + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e36f2eaf81..86004ef863 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1391,6 +1391,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 29de77e6d8..7ff7c0a2e4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; @@ -760,7 +761,7 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlCount = true; var query = scope.SqlContext.Query().Where(x => x.ParentId == root.Id); - var result = repository.GetPage(query, 0, 20, out var totalRecords, "UpdateDate", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 20, out var totalRecords, null, Ordering.By("UpdateDate")); Assert.AreEqual(25, totalRecords); foreach (var r in result) @@ -803,12 +804,12 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; scope.Database.AsUmbracoDatabase().EnableSqlCount = true; - var result = repository.GetPage(query, 0, 2, out var totalRecords, "title", Direction.Ascending, false); + var result = repository.GetPage(query, 0, 2, out var totalRecords, null, Ordering.By("title", isCustomField: true)); Assert.AreEqual(3, totalRecords); Assert.AreEqual(2, result.Count()); - result = repository.GetPage(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); + result = repository.GetPage(query, 1, 2, out totalRecords, null, Ordering.By("title", isCustomField: true)); Assert.AreEqual(1, result.Count()); } @@ -835,7 +836,7 @@ namespace Umbraco.Tests.Persistence.Repositories scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; scope.Database.AsUmbracoDatabase().EnableSqlCount = true; - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -858,7 +859,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 1, 1, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 1, 1, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -875,7 +876,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 0, 2, out var totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 2, out var totalRecords, null, Ordering.By("Name")); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(2)); @@ -892,7 +893,7 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = CreateRepository((IScopeAccessor)provider, out _); var query = scope.SqlContext.Query().Where(x => x.Level == 2); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Descending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name", Direction.Descending)); Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -911,7 +912,7 @@ namespace Umbraco.Tests.Persistence.Repositories var query = scope.SqlContext.Query().Where(x => x.Level == 2); var filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("Page 2")); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true, filterQuery); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(1)); Assert.That(result.Count(), Is.EqualTo(1)); @@ -930,7 +931,7 @@ namespace Umbraco.Tests.Persistence.Repositories var query = scope.SqlContext.Query().Where(x => x.Level == 2); var filterQuery = scope.SqlContext.Query().Where(x => x.Name.Contains("text")); - var result = repository.GetPage(query, 0, 1, out var totalRecords, "Name", Direction.Ascending, true, filterQuery); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filterQuery, Ordering.By("Name")); Assert.That(totalRecords, Is.EqualTo(2)); Assert.That(result.Count(), Is.EqualTo(1)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 87759db3df..d1e7f96ff3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -326,7 +326,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -348,7 +348,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 1, 1, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -370,7 +370,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 2, out totalRecords, null, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -392,7 +392,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, true); + var result = repository.GetPage(query, 0, 1, out totalRecords, null, Ordering.By("SortOrder", Direction.Descending)); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -413,8 +413,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPage(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); + var result = repository.GetPage(query, 0, 1, out var totalRecords, null, Ordering.By("Name")); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -435,10 +434,9 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; var filter = scope.SqlContext.Query().Where(x => x.Name.Contains("File")); - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, filter); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filter, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -454,15 +452,13 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - MediaTypeRepository mediaTypeRepository; - var repository = CreateRepository(provider, out mediaTypeRepository); + var repository = CreateRepository(provider, out _); // Act var query = scope.SqlContext.Query().Where(x => x.Level == 2); - long totalRecords; var filter = scope.SqlContext.Query().Where(x => x.Name.Contains("Test")); - var result = repository.GetPage(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, filter); + var result = repository.GetPage(query, 0, 1, out var totalRecords, filter, Ordering.By("SortOrder")); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a0ba648ddd..1bc8ff326d 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -457,10 +457,10 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "", - string cultureName = "") + string cultureName = "") // TODO it's not a NAME it's the ISO CODE { long totalChildren; - IContent[] children; + List children; if (pageNumber > 0 && pageSize > 0) { IQuery queryFilter = null; @@ -472,16 +472,14 @@ namespace Umbraco.Web.Editors } children = Services.ContentService - .GetPagedChildren( - id, (pageNumber - 1), pageSize, - out totalChildren, - orderBy, orderDirection, orderBySystemField, - queryFilter).ToArray(); + .GetPagedChildren(id, pageNumber - 1, pageSize, out totalChildren, + queryFilter, + Ordering.By(orderBy, orderDirection, cultureName, !orderBySystemField)).ToList(); } else { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; + children = Services.ContentService.GetChildren(id).ToList(); + totalChildren = children.Count; } if (totalChildren == 0) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index a39f034a39..d5d90b8479 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1277,7 +1277,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = new List(); foreach (var c in descendants) { @@ -1344,7 +1344,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => GetDto(m, false)).ToArray(); db.BulkInsertRecords(items); processed += items.Length; @@ -1402,7 +1402,7 @@ WHERE cmsContentNu.nodeId IN ( long total; do { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => GetDto(m, false)).ToArray(); db.BulkInsertRecords(items); processed += items.Length; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 5b640f13e5..5fa89e3f7b 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -1746,7 +1746,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); const bool published = true; // contentXml contains published content! var items = descendants.Select(c => new ContentXmlDto { NodeId = c.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, c, published).ToDataString() }).ToArray(); @@ -1819,7 +1819,7 @@ WHERE cmsPreviewXml.nodeId IN ( { // .GetPagedResultsByQuery implicitely adds ({Constants.DatabaseSchema.Tables.Document}.newest = 1) which // is what we want for preview (ie latest version of a content, published or not) - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); const bool published = true; // previewXml contains edit content! var items = descendants.Select(c => new PreviewXmlDto { @@ -1892,7 +1892,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.MediaService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, m).ToDataString() }).ToArray(); db.BulkInsertRecords(items); @@ -1961,7 +1961,7 @@ WHERE cmsContentXml.nodeId IN ( long total; do { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.DataTypeService, _serviceContext.LocalizationService, m).ToDataString() }).ToArray(); db.BulkInsertRecords(items); processed += items.Length;