diff --git a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs
index 42e1a0d415..a27657cbe0 100644
--- a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs
+++ b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs
@@ -113,6 +113,11 @@ namespace Umbraco.Core.Models
}
}
+ ///
+ /// When resolved from EntityService this checks if the entity has the HasChildren flag
+ ///
+ ///
+ ///
public static bool HasChildren(this IUmbracoEntity entity)
{
if (entity.AdditionalData.ContainsKey("HasChildren"))
@@ -133,6 +138,11 @@ namespace Umbraco.Core.Models
return entity.AdditionalData.GetValueIgnoreCase(key, defaultVal);
}
+ ///
+ /// When resolved from EntityService this checks if the entity has the IsContainer flag
+ ///
+ ///
+ ///
public static bool IsContainer(this IUmbracoEntity entity)
{
if (entity.AdditionalData.ContainsKeyIgnoreCase("IsContainer") == false) return false;
diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs
index 18073c088e..4eb3fe0659 100644
--- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs
@@ -50,7 +50,7 @@ namespace Umbraco.Core.Persistence.Factories
entity.ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty;
var publishedVersion = default(Guid);
- //some content items don't have a published version
+ //some content items don't have a published/newest version
if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null)
{
Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion);
diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs
index 3cde02c287..50df3183e1 100644
--- a/src/Umbraco.Core/Persistence/PetaPoco.cs
+++ b/src/Umbraco.Core/Persistence/PetaPoco.cs
@@ -703,7 +703,8 @@ namespace Umbraco.Core.Persistence
static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
- static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
+ static Regex rxGroupBy = new Regex(@"\bGROUP\s+BY\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?" clause
- m = rxOrderBy.Match(sqlCount);
+
+ // Look for an "ORDER BY " clause
+ m = rxOrderBy.Match(sqlCount);
if (!m.Success)
{
sqlOrderBy = null;
@@ -738,7 +738,15 @@ namespace Umbraco.Core.Persistence
sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length);
}
- return true;
+ // Look for an "GROUP BY " (end)
+ m = rxGroupBy.Match(sqlCount);
+ if (m.Success != false)
+ {
+ g = m.Groups[0];
+ sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length);
+ }
+
+ return true;
}
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
index 81c3944e31..ad421f835c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Models;
using Umbraco.Core;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
+using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -49,6 +50,96 @@ namespace Umbraco.Core.Persistence.Repositories
#region Query Methods
+ public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords,
+ string orderBy, Direction orderDirection, IQuery filter = null)
+ {
+ bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
+ bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
+
+ var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId);
+
+ var translator = new SqlTranslator(sqlClause, query);
+ var entitySql = translator.Translate();
+
+ var factory = new UmbracoEntityFactory();
+
+ //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData
+ var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id");
+
+ if (isMedia)
+ {
+ //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
+ var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql);
+ totalRecords = pagedResult.TotalItems;
+
+ var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000);
+ var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList();
+
+ //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before
+ foreach (var idGroup in ids)
+ {
+ var propSql = GetPropertySql(Constants.ObjectTypes.Media)
+ .Where("contentNodeId IN (@ids)", new {ids = idGroup})
+ .OrderBy("contentNodeId");
+
+ //This does NOT fetch all data into memory in a list, this will read
+ // over the records as a data reader, this is much better for performance and memory,
+ // but it means that during the reading of this data set, nothing else can be read
+ // from SQL server otherwise we'll get an exception.
+ var allPropertyData = _work.Database.Query(propSql);
+
+ //keep track of the current property data item being enumerated
+ var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
+ var hasCurrent = false; // initially there is no enumerator.Current
+
+ try
+ {
+ //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above,
+ // which allows us to more efficiently iterate over the large data set of property values.
+ foreach (var entity in entities)
+ {
+ // assemble the dtos for this def
+ // use the available enumerator.Current if any else move to next
+ while (hasCurrent || propertyDataSetEnumerator.MoveNext())
+ {
+ if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id)
+ {
+ hasCurrent = false; // enumerator.Current is not available
+
+ //the property data goes into the additional data
+ entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty
+ {
+ PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias,
+ Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext)
+ ? propertyDataSetEnumerator.Current.dataNvarchar
+ : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext)
+ };
+ }
+ else
+ {
+ hasCurrent = true; // enumerator.Current is available for another def
+ break; // no more propertyDataDto for this def
+ }
+ }
+ }
+ }
+ finally
+ {
+ propertyDataSetEnumerator.Dispose();
+ }
+ }
+
+ totalRecords = pagedResult.TotalItems;
+ return entities;
+ }
+ else
+ {
+ var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql);
+ totalRecords = pagedResult.TotalItems;
+ return pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList();
+ }
+ }
+
public IUmbracoEntity GetByKey(Guid key)
{
var sql = GetBaseWhere(GetBase, false, false, key);
@@ -71,8 +162,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (isMedia)
{
- //for now treat media differently
- //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
var entities = _work.Database.Fetch(
new UmbracoEntityRelator().Map, sql);
@@ -115,8 +205,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (isMedia)
{
- //for now treat media differently
- //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
var entities = _work.Database.Fetch(
new UmbracoEntityRelator().Map, sql);
@@ -171,8 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (isMedia)
{
- //for now treat media differently
- //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
var entities = _work.Database.Fetch(
new UmbracoEntityRelator().Map, sql);
foreach (var entity in entities)
@@ -231,8 +319,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
});
- //treat media differently for now
- //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
+ //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag!
var entities = _work.Database.Fetch(
new UmbracoEntityRelator().Map, mediaSql);
return entities;
@@ -278,11 +365,9 @@ namespace Umbraco.Core.Persistence.Repositories
return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter);
}
- private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null)
+ private Sql GetPropertySql(string nodeObjectType)
{
- //this will add any dataNvarchar property to the output which can be added to the additional properties
-
- var joinSql = new Sql()
+ var sql = new Sql()
.Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias")
.From()
.InnerJoin()
@@ -291,7 +376,16 @@ namespace Umbraco.Core.Persistence.Repositories
.On(dto => dto.Id, dto => dto.PropertyTypeId)
.InnerJoin()
.On(dto => dto.DataTypeId, dto => dto.DataTypeId)
- .Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media});
+ .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType });
+
+ return sql;
+ }
+
+ private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null)
+ {
+ //this will add any dataNvarchar property to the output which can be added to the additional properties
+
+ var joinSql = GetPropertySql(Constants.ObjectTypes.Media);
if (filter != null)
{
@@ -315,25 +409,30 @@ namespace Umbraco.Core.Persistence.Repositories
protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter)
{
var columns = new List