Include automatic relation type aliases from factory and fix SQL parameter overflow (#15141)

* Include automatic relation type aliases from factory

* Remove unnessecary distinct and fix SQL parameter overflow issue

* Fixed assertions and test distinct aliases

* Simplified collection assertions
This commit is contained in:
Ronald Barendse
2023-11-09 08:42:22 +01:00
committed by GitHub
parent d301679e53
commit 9f062e38eb
3 changed files with 132 additions and 65 deletions

View File

@@ -8,60 +8,119 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
{
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> 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<UmbracoEntityReference> GetAllReferences(
IPropertyCollection properties,
PropertyEditorCollection propertyEditors)
public ISet<UmbracoEntityReference> GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var trackedRelations = new HashSet<UmbracoEntityReference>();
var references = new HashSet<UmbracoEntityReference>();
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<UmbracoEntityReference> 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;
}
/// <summary>
/// Gets all relation type aliases that are automatically tracked.
/// </summary>
/// <param name="propertyEditors">The property editors.</param>
/// <returns>
/// All relation type aliases that are automatically tracked.
/// </returns>
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
{
// Always add default automatic relation types
var automaticRelationTypeAliases = new HashSet<string>(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
// Add relation types for all property editors
foreach (IDataEditor dataEditor in propertyEditors)
{
automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor));
}
return automaticRelationTypeAliases;
}
/// <summary>
/// Gets the relation type aliases that are automatically tracked for all properties.
/// </summary>
/// <param name="properties">The properties.</param>
/// <param name="propertyEditors">The property editors.</param>
/// <returns>
/// The relation type aliases that are automatically tracked for all properties.
/// </returns>
public ISet<string> GetAutomaticRelationTypesAliases(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
{
// Always add default automatic relation types
var automaticRelationTypeAliases = new HashSet<string>(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<string> 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;
}
}
}
}
}

View File

@@ -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<UmbracoEntityReference>();
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<NodeIdKey>(Sql()
var keyToIds = Database.FetchByGroups<NodeIdKey, Guid>(udiToGuids.Values, Constants.Sql.MaxParameterCount, guids => Sql()
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
.From<NodeDto>()
.WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.WhereIn<NodeDto>(x => x.UniqueId, guids))
.ToDictionary(x => x.UniqueId, x => x.NodeId);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())?
.ToDictionary(x => x.Alias, x => x);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>()).ToDictionary(x => x.Alias, x => x);
IEnumerable<ReadOnlyRelation> 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<string> GetAutomaticRelationTypesAliases(
IPropertyCollection properties,
PropertyEditorCollection propertyEditors)
{
var automaticRelationTypesAliases = new HashSet<string>(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;
}
/// <summary>
/// Inserts property values for the content entity
/// </summary>

View File

@@ -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<IDataValueReferenceFactory>);
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
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<string> GetAutomaticRelationTypesAliases() => new[]
{
"umbTest",
"umbTest", // Duplicate on purpose to test distinct aliases
};
}
}
}