Fixes n+1 queries for content and media types when resolving their properties and groups, this fixes the n+1 queries with the scheduled publishing too. Adds a test for template mappings, fixes call for GetAll on EntityController to not be called when there are no ids.

This commit is contained in:
Shannon
2014-09-02 11:50:04 +10:00
parent eec734783c
commit c21dcb0279
6 changed files with 192 additions and 113 deletions

View File

@@ -544,14 +544,8 @@ AND umbracoNode.id <> @id",
var mediaTypes = MapMediaTypes(mediaTypeIds, db, out allParentMediaTypeIds)
.ToArray();
foreach (var mediaType in mediaTypes)
{
var parentContentTypeIds = allParentMediaTypeIds[mediaType.Id];
MapContentTypeChildren(
mediaType, db, contentTypeRepository, parentContentTypeIds);
}
MapContentTypeChildren(mediaTypes, db, contentTypeRepository, allParentMediaTypeIds);
return mediaTypes;
}
@@ -566,68 +560,86 @@ AND umbracoNode.id <> @id",
var contentTypes = MapContentTypes(contentTypeIds, db, out allAssociatedTemplates, out allParentContentTypeIds)
.ToArray();
foreach (var contentType in contentTypes)
{
var associatedTemplates = allAssociatedTemplates[contentType.Id];
var parentContentTypeIds = allParentContentTypeIds[contentType.Id];
MapContentTypeTemplates(
contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates);
MapContentTypeTemplates(
contentType, db, contentTypeRepository, templateRepository, associatedTemplates);
MapContentTypeChildren(
contentType, db, contentTypeRepository, parentContentTypeIds);
}
MapContentTypeChildren(
contentTypes, db, contentTypeRepository, allParentContentTypeIds);
return contentTypes;
}
internal static void MapContentTypeChildren<TRepo>(IContentTypeComposition contentType,
internal static void MapContentTypeChildren<TRepo>(IContentTypeComposition[] contentTypes,
Database db,
TRepo contentTypeRepository,
IEnumerable<int> parentContentTypeIds)
IDictionary<int, IEnumerable<int>> allParentContentTypeIds)
where TRepo : IRepositoryQueryable<int, TEntity>
{
//NOTE: SQL call #2
PropertyGroupCollection propGroups;
PropertyTypeCollection propTypes;
MapGroupsAndProperties(contentType.Id, db, out propTypes, out propGroups);
contentType.PropertyGroups = propGroups;
((ContentTypeBase)contentType).PropertyTypes = propTypes;
var ids = contentTypes.Select(x => x.Id).ToArray();
IDictionary<int, PropertyGroupCollection> allPropGroups;
IDictionary<int, PropertyTypeCollection> allPropTypes;
MapGroupsAndProperties(ids, db, out allPropTypes, out allPropGroups);
foreach (var contentType in contentTypes)
{
contentType.PropertyGroups = allPropGroups[contentType.Id];
((ContentTypeBase) contentType).PropertyTypes = allPropTypes[contentType.Id];
}
//NOTE: SQL call #3++
var parentIdsAsArray = parentContentTypeIds.ToArray();
if (parentIdsAsArray.Any())
var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray();
if (allParentIdsAsArray.Any())
{
var parentContentTypes = contentTypeRepository.GetAll(parentIdsAsArray);
foreach (var parentContentType in parentContentTypes)
var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray();
foreach (var contentType in contentTypes)
{
var result = contentType.AddContentType(parentContentType);
//Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference)
var parentContentTypes = allParentContentTypes.Where(x => allParentContentTypeIds[contentType.Id].Contains(x.Id));
foreach (var parentContentType in parentContentTypes)
{
var result = contentType.AddContentType(parentContentType);
//Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference)
}
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)contentType).ResetDirtyProperties(false);
}
}
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)contentType).ResetDirtyProperties(false);
}
internal static void MapContentTypeTemplates<TRepo>(IContentType contentType,
internal static void MapContentTypeTemplates<TRepo>(IContentType[] contentTypes,
Database db,
TRepo contentTypeRepository,
ITemplateRepository templateRepository,
IEnumerable<AssociatedTemplate> associatedTemplates)
IDictionary<int, IEnumerable<AssociatedTemplate>> associatedTemplates)
where TRepo : IRepositoryQueryable<int, TEntity>
{
//NOTE: SQL call #3++
//SEE: http://issues.umbraco.org/issue/U4-5174 to fix this
var templateIds = associatedTemplates.Select(x => x.TemplateId).ToArray();
var templates = templateIds.Any()
? templateRepository.GetAll()
: Enumerable.Empty<ITemplate>();
contentType.AllowedTemplates = templates.ToArray();
var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId)
.Distinct()
.ToArray();
var templates = (templateIds.Any()
? templateRepository.GetAll(templateIds)
: Enumerable.Empty<ITemplate>()).ToArray();
foreach (var contentType in contentTypes)
{
var associatedTemplateIds = associatedTemplates[contentType.Id].Select(x => x.TemplateId)
.Distinct()
.ToArray();
contentType.AllowedTemplates = (associatedTemplateIds.Any()
? templates.Where(x => associatedTemplateIds.Contains(x.Id))
: Enumerable.Empty<ITemplate>()).ToArray();
}
}
internal static IEnumerable<IMediaType> MapMediaTypes(int[] mediaTypeIds, Database db,
@@ -662,7 +674,6 @@ AND umbracoNode.id <> @id",
AND (umbracoNode.id IN (@contentTypeIds))";
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
// since there will be 2 params per single row it will be half that amount
if ((mediaTypeIds.Length - 1) > 2000)
throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
@@ -781,7 +792,6 @@ AND umbracoNode.id <> @id",
AND (umbracoNode.id IN (@contentTypeIds))";
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
// since there will be 2 params per single row it will be half that amount
if ((contentTypeIds.Length - 1) > 2000)
throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
@@ -910,15 +920,17 @@ AND umbracoNode.id <> @id",
.Select(x => x.Value).ToList());
}
internal static void MapGroupsAndProperties(int contentTypeId, Database db,
out PropertyTypeCollection propertyTypeCollection,
out PropertyGroupCollection propertyGroupCollection)
{
internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db,
out IDictionary<int, PropertyTypeCollection> allPropertyTypeCollection,
out IDictionary<int, PropertyGroupCollection> allPropertyGroupCollection)
{
// first part Gets all property groups including property type data even when no property type exists on the group
// second part Gets all property types including ones that are not on a group
// therefore the union of the two contains all of the property type and property group information we need
var sql = @"SELECT PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptHelpText,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp,
var sql = @"SELECT PG.contenttypeNodeId as contentTypeId,
PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptHelpText,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp,
PT.dtId,PT.dtDbType,PT.dtPropEdAlias,
PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("text") + @" as pgText
FROM cmsPropertyTypeGroup as PG
@@ -933,11 +945,12 @@ AND umbracoNode.id <> @id",
ON PT.dataTypeId = DT.nodeId
) as PT
ON PT.ptGroupId = PG.id
WHERE (PG.contenttypeNodeId = @contentTypeId)
WHERE (PG.contenttypeNodeId in (@contentTypeIds))
UNION
SELECT PT.id as ptId, PT.Alias as ptAlias, PT." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Description") + @" as ptDesc, PT.helpText as ptHelpText,
SELECT PT.contentTypeId as contentTypeId,
PT.id as ptId, PT.Alias as ptAlias, PT." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Description") + @" as ptDesc, PT.helpText as ptHelpText,
PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp,
DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias,
PG.id as pgId, PG.parentGroupId as pgParentGroupId, PG.sortorder as pgSortOrder, PG." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("text") + @" as pgText
@@ -946,67 +959,90 @@ AND umbracoNode.id <> @id",
ON PT.dataTypeId = DT.[nodeId]
LEFT JOIN cmsPropertyTypeGroup as PG
ON PG.[id] = PT.propertyTypeGroupId
WHERE (PT.contentTypeId = @contentTypeId)
WHERE (PT.contentTypeId in (@contentTypeIds))
ORDER BY (PG.id)";
var result = db.Fetch<dynamic>(sql, new { contentTypeId = contentTypeId });
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
// Since there are 2 groups of params, it will be half!
if (((contentTypeIds.Length / 2) - 1) > 2000)
throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
//from this we need to make :
// * PropertyGroupCollection - Contains all property groups along with all property types associated with a group
// * PropertyTypeCollection - Contains all property types that do not belong to a group
var result = db.Fetch<dynamic>(sql, new { contentTypeIds = contentTypeIds });
//create the property group collection first, this means all groups (even empty ones) and all groups with properties
allPropertyGroupCollection = new Dictionary<int, PropertyGroupCollection>();
allPropertyTypeCollection = new Dictionary<int, PropertyTypeCollection>();
propertyGroupCollection = new PropertyGroupCollection(result
//get all rows that have a group id
.Where(x => x.pgId != null)
//turn that into a custom object containing only the group info
.Select(x => new { GroupId = x.pgId, ParentGroupId = x.pgParentGroupId, SortOrder = x.pgSortOrder, Text = x.pgText })
//get distinct data by id
.DistinctBy(x => (int)x.GroupId)
//for each of these groups, create a group object with it's associated properties
.Select(group => new PropertyGroup(new PropertyTypeCollection(
result
.Where(row => row.pgId == group.GroupId && row.ptId != null)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType))
{
//fill in the rest of the property type properties
Alias = row.ptAlias,
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Mandatory = row.ptMandatory,
Name = row.ptName,
PropertyGroupId = new Lazy<int>(() => group.GroupId, false),
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
})))
{
//fill in the rest of the group properties
Id = group.GroupId,
Name = group.Text,
ParentId = group.ParentGroupId,
SortOrder = group.SortOrder
}).ToArray());
foreach (var contentTypeId in contentTypeIds)
{
//from this we need to make :
// * PropertyGroupCollection - Contains all property groups along with all property types associated with a group
// * PropertyTypeCollection - Contains all property types that do not belong to a group
//Create the property type collection now (that don't have groups)
//create the property group collection first, this means all groups (even empty ones) and all groups with properties
propertyTypeCollection = new PropertyTypeCollection(result
.Where(x => x.pgId == null)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType))
{
//fill in the rest of the property type properties
Alias = row.ptAlias,
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Mandatory = row.ptMandatory,
Name = row.ptName,
PropertyGroupId = null,
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
}).ToArray());
int currId = contentTypeId;
var propertyGroupCollection = new PropertyGroupCollection(result
//get all rows that have a group id
.Where(x => x.pgId != null)
//filter based on the current content type
.Where(x => x.contentTypeId == currId)
//turn that into a custom object containing only the group info
.Select(x => new { GroupId = x.pgId, ParentGroupId = x.pgParentGroupId, SortOrder = x.pgSortOrder, Text = x.pgText })
//get distinct data by id
.DistinctBy(x => (int)x.GroupId)
//for each of these groups, create a group object with it's associated properties
.Select(group => new PropertyGroup(new PropertyTypeCollection(
result
.Where(row => row.pgId == group.GroupId && row.ptId != null)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType))
{
//fill in the rest of the property type properties
Alias = row.ptAlias,
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Mandatory = row.ptMandatory,
Name = row.ptName,
PropertyGroupId = new Lazy<int>(() => group.GroupId, false),
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
})))
{
//fill in the rest of the group properties
Id = group.GroupId,
Name = group.Text,
ParentId = group.ParentGroupId,
SortOrder = group.SortOrder
}).ToArray());
allPropertyGroupCollection[currId] = propertyGroupCollection;
//Create the property type collection now (that don't have groups)
var propertyTypeCollection = new PropertyTypeCollection(result
.Where(x => x.pgId == null)
//filter based on the current content type
.Where(x => x.contentTypeId == currId)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType))
{
//fill in the rest of the property type properties
Alias = row.ptAlias,
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Mandatory = row.ptMandatory,
Name = row.ptName,
PropertyGroupId = null,
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
}).ToArray());
allPropertyTypeCollection[currId] = propertyTypeCollection;
}
}
}

View File

@@ -170,9 +170,12 @@ namespace Umbraco.Tests.Persistence.Querying
transaction.Complete();
}
PropertyTypeCollection propTypeCollection;
PropertyGroupCollection propGroupCollection;
ContentTypeRepository.ContentTypeQueryMapper.MapGroupsAndProperties(99999, DatabaseContext.Database, out propTypeCollection, out propGroupCollection);
IDictionary<int, PropertyTypeCollection> allPropTypeCollection;
IDictionary<int, PropertyGroupCollection> allPropGroupCollection;
ContentTypeRepository.ContentTypeQueryMapper.MapGroupsAndProperties(new[] { 99999 }, DatabaseContext.Database, out allPropTypeCollection, out allPropGroupCollection);
var propGroupCollection = allPropGroupCollection[99999];
var propTypeCollection = allPropTypeCollection[99999];
Assert.AreEqual(4, propGroupCollection.Count);
Assert.AreEqual(2, propGroupCollection["Group1"].PropertyTypes.Count);

View File

@@ -65,6 +65,42 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(repository, Is.Not.Null);
}
[Test]
public void Maps_Templates_Correctly()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
using (var templateRepo = new TemplateRepository(unitOfWork))
using (var repository = CreateRepository(unitOfWork))
{
var templates = new[]
{
new Template("test1.cshtml", "test1", "test1"),
new Template("test2.cshtml", "test2", "test2"),
new Template("test3.cshtml", "test3", "test3")
};
foreach (var template in templates)
{
templateRepo.AddOrUpdate(template);
}
unitOfWork.Commit();
var contentType = MockedContentTypes.CreateSimpleContentType();
contentType.AllowedTemplates = new[] {templates[0], templates[1]};
contentType.SetDefaultTemplate(templates[0]);
repository.AddOrUpdate(contentType);
unitOfWork.Commit();
//re-get
var result = repository.Get(contentType.Id);
Assert.AreEqual(2, result.AllowedTemplates.Count());
Assert.AreEqual(templates[0].Id, result.DefaultTemplate.Id);
}
}
[Test]
public void Can_Perform_Add_On_ContentTypeRepository()
{

View File

@@ -70,8 +70,9 @@ namespace Umbraco.Web.Editors
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
types = contentItem.ContentType.AllowedContentTypes
.Select(x => Services.ContentTypeService.GetContentType(x.Id.Value))
types = Services.ContentTypeService.GetAllContentTypes(
contentItem.ContentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray())
.ToList();
}

View File

@@ -566,6 +566,8 @@ namespace Umbraco.Web.Editors
private IEnumerable<EntityBasic> GetResultForKeys(IEnumerable<Guid> keys, UmbracoEntityTypes entityType)
{
if (keys.Any() == false) return Enumerable.Empty<EntityBasic>();
var objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
@@ -591,6 +593,8 @@ namespace Umbraco.Web.Editors
private IEnumerable<EntityBasic> GetResultForIds(IEnumerable<int> ids, UmbracoEntityTypes entityType)
{
if (ids.Any() == false) return Enumerable.Empty<EntityBasic>();
var objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{

View File

@@ -61,10 +61,9 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return contentItem.ContentType.AllowedContentTypes
.Select(x => Services.ContentTypeService.GetMediaType((int) x.Id.Value))
.Select(Mapper.Map<IMediaType, ContentTypeBasic>);
return Services.ContentTypeService.GetAllMediaTypes(
contentItem.ContentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray())
.Select(Mapper.Map<IMediaType, ContentTypeBasic>);
}
}