diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index 24d6f17eb0..c605d45a9a 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -8,60 +8,119 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase> items) : base(items) - { - } + { } // TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented // by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection - public IEnumerable GetAllReferences( - IPropertyCollection properties, - PropertyEditorCollection propertyEditors) + public ISet GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors) { - var trackedRelations = new HashSet(); + var references = new HashSet(); - foreach (IProperty p in properties) + foreach (IProperty property in properties) { - if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor)) + if (!propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor)) { continue; } // TODO: We will need to change this once we support tracking via variants/segments // for now, we are tracking values from ALL variants - foreach (IPropertyValue propertyVal in p.Values) + foreach (IPropertyValue propertyValue in property.Values) { - var val = propertyVal.EditedValue; + object? value = propertyValue.EditedValue; - IDataValueEditor? valueEditor = editor?.GetValueEditor(); - if (valueEditor is IDataValueReference reference) + if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference) { - IEnumerable refs = reference.GetReferences(val); - foreach (UmbracoEntityReference r in refs) - { - trackedRelations.Add(r); - } + references.UnionWith(dataValueReference.GetReferences(value)); } // Loop over collection that may be add to existing property editors // implementation of GetReferences in IDataValueReference. // Allows developers to add support for references by a // package /property editor that did not implement IDataValueReference themselves - foreach (IDataValueReferenceFactory item in this) + foreach (IDataValueReferenceFactory dataValueReferenceFactory in this) { // Check if this value reference is for this datatype/editor // Then call it's GetReferences method - to see if the value stored - // in the dataeditor/property has referecnes to media/content items - if (item.IsForEditor(editor)) + // in the dataeditor/property has references to media/content items + if (dataValueReferenceFactory.IsForEditor(dataEditor)) { - foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val)) - { - trackedRelations.Add(r); - } + references.UnionWith(dataValueReferenceFactory.GetDataValueReference().GetReferences(value)); } } } } - return trackedRelations; + return references; + } + + /// + /// Gets all relation type aliases that are automatically tracked. + /// + /// The property editors. + /// + /// All relation type aliases that are automatically tracked. + /// + public ISet GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) + { + // Always add default automatic relation types + var automaticRelationTypeAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + // Add relation types for all property editors + foreach (IDataEditor dataEditor in propertyEditors) + { + automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor)); + } + + return automaticRelationTypeAliases; + } + + /// + /// Gets the relation type aliases that are automatically tracked for all properties. + /// + /// The properties. + /// The property editors. + /// + /// The relation type aliases that are automatically tracked for all properties. + /// + public ISet GetAutomaticRelationTypesAliases(IPropertyCollection properties, PropertyEditorCollection propertyEditors) + { + // Always add default automatic relation types + var automaticRelationTypeAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); + + // Only add relation types that are used in the properties + foreach (IProperty property in properties) + { + if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor)) + { + automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor)); + } + } + + return automaticRelationTypeAliases; + } + + private IEnumerable GetAutomaticRelationTypesAliases(IDataEditor dataEditor) + { + if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference) + { + // Return custom relation types from value editor implementation + foreach (var alias in dataValueReference.GetAutomaticRelationTypesAliases()) + { + yield return alias; + } + } + + foreach (IDataValueReferenceFactory dataValueReferenceFactory in this) + { + if (dataValueReferenceFactory.IsForEditor(dataEditor)) + { + // Return custom relation types from factory + foreach (var alias in dataValueReferenceFactory.GetDataValueReference().GetAutomaticRelationTypesAliases()) + { + yield return alias; + } + } + } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index de247a6120..fde1c6ca05 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1083,13 +1083,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected void PersistRelations(TEntity entity) { // Get all references from our core built in DataEditors/Property Editors - // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection - var trackedRelations = new List(); - trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors)); - - var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray(); + // Along with seeing if developers want to collect additional references from the DataValueReferenceFactories collection + var trackedRelations = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors); // First delete all auto-relations for this entity + var relationTypeAliases = _dataValueReferenceFactories.GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray(); RelationRepository.DeleteByParent(entity.Id, relationTypeAliases); if (trackedRelations.Count == 0) @@ -1097,23 +1095,20 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return; } - trackedRelations = trackedRelations.Distinct().ToList(); - var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi) - .ToDictionary(x => (Udi)x!, x => x!.Guid); + var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi).WhereNotNull().ToDictionary(x => (Udi)x, x => x.Guid); // lookup in the DB all INT ids for the GUIDs and chuck into a dictionary - var keyToIds = Database.Fetch(Sql() + var keyToIds = Database.FetchByGroups(udiToGuids.Values, Constants.Sql.MaxParameterCount, guids => Sql() .Select(x => x.NodeId, x => x.UniqueId) .From() - .WhereIn(x => x.UniqueId, udiToGuids.Values)) + .WhereIn(x => x.UniqueId, guids)) .ToDictionary(x => x.UniqueId, x => x.NodeId); - var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty())? - .ToDictionary(x => x.Alias, x => x); + var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty()).ToDictionary(x => x.Alias, x => x); IEnumerable toSave = trackedRelations.Select(rel => { - if (allRelationTypes is null || !allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType)) + if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType)) { throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist"); } @@ -1135,31 +1130,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement RelationRepository.SaveBulk(toSave); } - private IEnumerable GetAutomaticRelationTypesAliases( - IPropertyCollection properties, - PropertyEditorCollection propertyEditors) - { - var automaticRelationTypesAliases = new HashSet(Constants.Conventions.RelationTypes.AutomaticRelationTypes); - - foreach (IProperty property in properties) - { - if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false ) - { - continue; - } - - if (editor.GetValueEditor() is IDataValueReference reference) - { - foreach (var alias in reference.GetAutomaticRelationTypesAliases()) - { - automaticRelationTypesAliases.Add(alias); - } - } - } - - return automaticRelationTypesAliases; - } - /// /// Inserts property values for the content entity /// diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs index 6108f59e2e..cff072873f 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -173,6 +171,40 @@ public class DataValueReferenceFactoryCollectionTests Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString()); } + [Test] + public void GetAutomaticRelationTypesAliases_ContainsDefault() + { + var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty)); + var properties = new PropertyCollection(); + + var resultA = collection.GetAutomaticRelationTypesAliases(propertyEditors).ToArray(); + var resultB = collection.GetAutomaticRelationTypesAliases(properties, propertyEditors).ToArray(); + + var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes; + CollectionAssert.AreEquivalent(expected, resultA, "Result A does not contain the expected relation type aliases."); + CollectionAssert.AreEquivalent(expected, resultB, "Result B does not contain the expected relation type aliases."); + } + + [Test] + public void GetAutomaticRelationTypesAliases_ContainsCustom() + { + var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield()); + + var labelPropertyEditor = new LabelPropertyEditor(DataValueEditorFactory, IOHelper, EditorConfigurationParser); + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => labelPropertyEditor.Yield())); + var serializer = new ConfigurationEditorJsonSerializer(); + var property = new Property(new PropertyType(ShortStringHelper, new DataType(labelPropertyEditor, serializer))); + var properties = new PropertyCollection { property, property }; // Duplicate on purpose to test distinct aliases + + var resultA = collection.GetAutomaticRelationTypesAliases(propertyEditors).ToArray(); + var resultB = collection.GetAutomaticRelationTypesAliases(properties, propertyEditors).ToArray(); + + var expected = Constants.Conventions.RelationTypes.AutomaticRelationTypes.Append("umbTest"); + CollectionAssert.AreEquivalent(expected, resultA, "Result A does not contain the expected relation type aliases."); + CollectionAssert.AreEquivalent(expected, resultB, "Result B does not contain the expected relation type aliases."); + } + private class TestDataValueReferenceFactory : IDataValueReferenceFactory { public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference(); @@ -196,6 +228,12 @@ public class DataValueReferenceFactoryCollectionTests yield return new UmbracoEntityReference(udi); } } + + public IEnumerable GetAutomaticRelationTypesAliases() => new[] + { + "umbTest", + "umbTest", // Duplicate on purpose to test distinct aliases + }; } } }