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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user