Skip to content

Commit

Permalink
Creates method to create the relations based on the property editor's…
Browse files Browse the repository at this point in the history
… returned reference but have discovered a gotcha for relations, so next step is to resolve that.
  • Loading branch information
Shazwazza committed Oct 24, 2019
1 parent 9303a49 commit 193892f
Show file tree
Hide file tree
Showing 30 changed files with 213 additions and 37 deletions.
32 changes: 26 additions & 6 deletions src/Umbraco.Core/Constants-Conventions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,32 +309,52 @@ public static class Url
public static class RelationTypes
{
/// <summary>
/// ContentType name for default relation type "Relate Document On Copy".
/// Name for default relation type "Related Media".
/// </summary>
public const string RelatedMediaName = "Related Media";

/// <summary>
/// Alias for default relation type "Related Media"
/// </summary>
public const string RelatedMediaAlias = "umbMedia";

/// <summary>
/// Name for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentName = "Related Document";

/// <summary>
/// Alias for default relation type "Related Document"
/// </summary>
public const string RelatedDocumentAlias = "umbDocument";

/// <summary>
/// Name for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyName = "Relate Document On Copy";

/// <summary>
/// ContentType alias for default relation type "Relate Document On Copy".
/// Alias for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";

/// <summary>
/// ContentType name for default relation type "Relate Parent Document On Delete".
/// Name for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";

/// <summary>
/// ContentType alias for default relation type "Relate Parent Document On Delete".
/// Alias for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";

/// <summary>
/// ContentType name for default relation type "Relate Parent Media Folder On Delete".
/// Name for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete";

/// <summary>
/// ContentType alias for default relation type "Relate Parent Media Folder On Delete".
/// Alias for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete";
}
Expand Down
10 changes: 10 additions & 0 deletions src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,16 @@ private void CreateRelationTypeData()
relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);

//TODO: We need to decide if we are going to change the relations APIs since it's pretty crappy that we have to explicitly define all relation type object type combinations...

relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);

relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName };
relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
}

private void CreateKeyValueData()
Expand Down
1 change: 1 addition & 0 deletions src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Umbraco.Core.Models.Editors
{

/// <summary>
/// Represents an uploaded file for a property.
/// </summary>
Expand Down
55 changes: 55 additions & 0 deletions src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;

namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Used to track reference to other entities in a property value
/// </summary>
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
{
private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(Udi.UnknownTypeUdi.Instance, string.Empty);

public UmbracoEntityReference(Udi udi, string relationTypeAlias)
{
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
}

public static UmbracoEntityReference Empty() => _empty;

public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();

public Udi Udi { get; }
public string RelationTypeAlias { get; }

public override bool Equals(object obj)
{
return obj is UmbracoEntityReference reference && Equals(reference);
}

public bool Equals(UmbracoEntityReference other)
{
return EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
}

public override int GetHashCode()
{
var hashCode = -487348478;
hashCode = hashCode * -1521134295 + EqualityComparer<Udi>.Default.GetHashCode(Udi);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(RelationTypeAlias);
return hashCode;
}

public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right)
{
return left.Equals(right);
}

public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right)
{
return !(left == right);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IRelationRepository : IReadWriteQueryRepository<int, IRelation>
{

/// <summary>
/// Deletes all relations for a parent for any specified relation type alias
/// </summary>
/// <param name="parentId"></param>
/// <param name="relationTypeAliases">
/// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted
/// </param>
void DeleteByParent(int parentId, params string[] relationTypeAliases);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
Expand Down Expand Up @@ -47,19 +48,21 @@ internal abstract class ContentRepositoryBase<TId, TEntity, TRepository> : NPoco
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
ILanguageRepository languageRepository, IRelationRepository relationRepository,
ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors)
: base(scopeAccessor, cache, logger)
{
LanguageRepository = languageRepository;
RelationRepository = relationRepository;
RelationTypeRepository = relationTypeRepository;
_propertyEditors = propertyEditors;
}

protected abstract TRepository This { get; }

protected ILanguageRepository LanguageRepository { get; }
protected IRelationRepository RelationRepository { get; }
protected IRelationTypeRepository RelationTypeRepository { get; }

protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value;

Expand Down Expand Up @@ -818,7 +821,62 @@ public virtual IEnumerable<TEntity> GetRecycleBin()

protected void PersistRelations(TEntity entity)
{
//foreach(var p in entity.)
var trackedRelations = new List<UmbracoEntityReference>();

foreach (var p in entity.Properties)
{
if (!PropertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue;
if (!(editor is IDataValueReference reference)) continue;

//TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here
if (!p.PropertyType.VariesByNothing()) continue;

var val = p.GetValue(); // get the invariant value
var refs = reference.GetReferences(val);
trackedRelations.AddRange(refs);
}

if (trackedRelations.Count == 0) return;

//First delete all relations for this entity
var relationTypes = trackedRelations.Select(x => x.RelationTypeAlias).ToArray();
RelationRepository.DeleteByParent(entity.Id, relationTypes);

var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
.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().Select<NodeDto>(x => x.NodeId, x => x.UniqueId).From<NodeDto>().WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.ToDictionary(x => x.UniqueId, x => x.NodeId);

var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())
.ToDictionary(x => x.Alias, x => x);

foreach(var rel in trackedRelations)
{
if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType))
throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");

if (!udiToGuids.TryGetValue(rel.Udi, out var guid))
continue; // This shouldn't happen!

if (!keyToIds.TryGetValue(guid, out var id))
continue; // This shouldn't happen!

//Create new relation
//TODO: This is N+1, we could do this all in one operation, just need a new method on the relations repo
RelationRepository.Save(new Relation(entity.Id, id, relationType));
}

}

private class NodeIdKey
{
[Column("id")]
public int NodeId { get; set; }

[Column("uniqueId")]
public Guid UniqueId { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository
{
public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, propertyEditorCollection)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ internal class DocumentRepository : ContentRepositoryBase<int, IContent, Documen
/// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
/// </param>
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors)
: base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, propertyEditors)
: base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors)
{
_contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository));
_templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ internal class MediaRepository : ContentRepositoryBase<int, IMedia, MediaReposit
private readonly ITagRepository _tagRepository;
private readonly MediaByGuidReadRepository _mediaByGuidReadRepository;

public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository,
public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditorCollection)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditorCollection)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection)
{
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ internal class MemberRepository : ContentRepositoryBase<int, IMember, MemberRepo
private readonly IMemberGroupRepository _memberGroupRepository;

public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository,
IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> propertyEditors)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, propertyEditors)
: base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors)
{
_memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;

namespace Umbraco.Core.Persistence.Repositories.Implement
Expand Down Expand Up @@ -157,5 +158,20 @@ protected override void PersistUpdatedItem(IRelation entity)
}

#endregion

public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
var subQuery = Sql().Select<RelationDto>(x => x.Id)
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == parentId);

if (relationTypeAliases.Length > 0)
{
subQuery.WhereIn<RelationTypeDto>(x => x.Alias, relationTypeAliases);
}

Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public static string GetQuotedColumn(this ISqlSyntaxProvider sql, string tableNa
/// </remarks>
public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In)
{
//TODO: This is no longer necessary since this used to be a specific requirement for MySql!
// Now we can do a Delete<T> + sub query, see RelationRepository.DeleteByParent for example

return
new Sql(string.Format(
Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Core/PropertyEditors/IDataValueReference.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Editors;

namespace Umbraco.Core.PropertyEditors
{
Expand All @@ -12,6 +13,6 @@ public interface IDataValueReference
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
IEnumerable<Udi> GetReferences(object value);
IEnumerable<UmbracoEntityReference> GetReferences(object value);
}
}
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/IRelationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public interface IRelationService : IService
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType);
IEnumerable<IRelation> GetAllRelationsByRelationType(IRelationType relationType);

/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
Expand Down
Loading

0 comments on commit 193892f

Please sign in to comment.