diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 857e059942..6e34030b29 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -128,6 +128,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dtos = page.Items; var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + //TODO: For isContent will we need to build up the variation info? + if (isMedia) BuildProperties(entities, dtos); @@ -251,18 +253,35 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetBaseWhere(isContent, isMedia, false, null, objectType); + var translator = new SqlTranslator(sql, query); sql = translator.Translate(); sql = AddGroupBy(isContent, isMedia, sql); - var dtos = Database.Fetch(sql); - if (dtos.Count == 0) return Enumerable.Empty(); - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var dtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + if (dtos.Count == 0) return Enumerable.Empty(); + var entities = dtos.Select(x => BuildDocumentEntity(x)).ToArray(); + return entities; + } + else + { + var dtos = Database.Fetch(sql); - if (isMedia) - BuildProperties(entities, dtos); + if (dtos.Count == 0) return Enumerable.Empty(); - return entities; + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + + if (isMedia) + BuildProperties(entities, dtos); + + return entities; + } } public UmbracoObjectTypes GetObjectType(int id) @@ -409,8 +428,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // return wrappedSql; //} - // the DTO corresponding to fields selected by GetBase + + /// + /// The DTO used to fetch results for a content item with it's variation info + /// + private class ContentEntityDto : BaseDto + { + [ResultColumn, Reference(ReferenceType.Many)] + public List VariationInfo { get; set; } + } + + /// + /// The DTO used in the 1:M result for content variation info + /// + private class ContentEntityVariationInfoDto + { + [Column("versionCultureId")] + public int VersionCultureId { get; set; } + [Column("versionCultureLangId")] + public int LanguageId { get; set; } + [Column("versionCultureName")] + public string Name { get; set; } + } + // ReSharper disable once ClassNeverInstantiated.Local + /// + /// the DTO corresponding to fields selected by GetBase + /// private class BaseDto { // ReSharper disable UnusedAutoPropertyAccessor.Local @@ -455,14 +499,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndSelect(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate) .Append(", COUNT(child.id) AS children"); - if (isContent) - sql - .AndSelect(x => x.Published, x => x.Edited); - if (isContent || isMedia) sql .AndSelect(x => NPocoSqlExtensions.Statics.Alias(x.Id, "versionId")) .AndSelect(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer); + + if (isContent) + { + sql + .AndSelect(x => x.Published, x => x.Edited) + //This MUST come last in the select statements since we will end up with a 1:M query + .AndSelect( + x => NPocoSqlExtensions.Statics.Alias(x.Id, "versionCultureId"), + x => NPocoSqlExtensions.Statics.Alias(x.LanguageId, "versionCultureLangId"), + x => NPocoSqlExtensions.Statics.Alias(x.Name, "versionCultureName")); + } } sql @@ -482,10 +533,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.NodeId == right.NodeId); } + //Any LeftJoin statements need to come last if (isCount == false) + { sql .LeftJoin("child").On((left, right) => left.NodeId == right.ParentId, aliasRight: "child"); + if (isContent) + sql + .LeftJoin().On((left, right) => left.Id == right.VersionId); + } + + filter?.Invoke(sql); return sql; @@ -542,8 +601,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndBy(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate); if (isContent) + { sql - .AndBy(x => x.Published, x => x.Edited); + .AndBy(x => x.Published, x => x.Edited) + .AndBy(x => x.Id, x => x.LanguageId, x => x.Name); + } + if (isContent || isMedia) sql @@ -819,6 +882,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return entity; } + /// + /// Builds the from a and ensures the AdditionalData is populated with variant info + /// + /// + /// + private static EntitySlim BuildDocumentEntity(ContentEntityDto dto) + { + // EntitySlim does not track changes + var entity = new DocumentEntitySlim(); + BuildDocumentEntity(entity, dto); + var variantInfo = new Dictionary(); + if (dto.VariationInfo != null) + { + foreach (var info in dto.VariationInfo) + { + variantInfo[info.LanguageId] = info.Name; + } + entity.AdditionalData["VariantInfo"] = variantInfo; + } + return entity; + } + #endregion } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs deleted file mode 100644 index 2357ba273a..0000000000 --- a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Tests.Persistence.Repositories -{ - class EntityRepositoryTests - { - } -} diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index b8d24d940c..d7acc6e177 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -427,6 +427,60 @@ namespace Umbraco.Tests.Services Assert.That(entities.Any(), Is.True); Assert.That(entities.Length, Is.EqualTo(1)); Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType_With_Variant_Names() + { + var service = ServiceContext.EntityService; + + var langFr = new Language("fr-FR"); + var langEs = new Language("es-ES"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langEs); + + var contentType = MockedContentTypes.CreateSimpleContentType("test1", "Test1", false); + contentType.Variations = ContentVariation.CultureNeutral; + ServiceContext.ContentTypeService.Save(contentType); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + if (i % 2 == 0) + { + c1.SetName(langFr.Id, "Test " + i + " - FR"); + c1.SetName(langEs.Id, "Test " + i + " - ES"); + } + ServiceContext.ContentService.Save(c1); + } + + var entities = service.GetChildren(root.Id, UmbracoObjectTypes.Document).ToArray(); + + Assert.AreEqual(10, entities.Length); + + for (int i = 0; i < entities.Length; i++) + { + if (i % 2 == 0) + { + Assert.AreEqual(1, entities[i].AdditionalData.Count); + Assert.AreEqual("VariantInfo", entities[i].AdditionalData.Keys.First()); + var variantInfo = entities[i].AdditionalData.First().Value as IDictionary; + Assert.IsNotNull(variantInfo); + var keys = variantInfo.Keys.ToList(); + var vals = variantInfo.Values.ToList(); + Assert.AreEqual(langFr.Id, keys[0]); + Assert.AreEqual("Test " + i + " - FR", vals[0]); + Assert.AreEqual(langEs.Id, keys[1]); + Assert.AreEqual("Test " + i + " - ES", vals[1]); + } + else + { + Assert.AreEqual(0, entities[i].AdditionalData.Count); + } + } } [Test] @@ -518,7 +572,7 @@ namespace Umbraco.Tests.Services } Assert.That(entities.Any(x => - x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim) y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); + x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim)y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); } [Test] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f630194a41..3da04b48ca 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -143,7 +143,6 @@ - @@ -606,11 +605,9 @@ $(NuGetPackageFolders.Split(';')[0]) - - - + @@ -624,5 +621,4 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index b6ab625b78..488b1097c0 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -217,7 +217,7 @@ namespace Umbraco.Web.Trees // we'll mock using this and it will just be some mock data foreach(var e in result) { - if (e.AdditionalData.TryGetValue("VariantNames", out var variantNames)) + if (e.AdditionalData.TryGetValue("VariantInfo", out var variantNames)) { var casted = (IDictionary)variantNames; e.Name = casted[langId.Value];