Adds ability to bulk insert relations and adds tests

This commit is contained in:
Shannon
2019-11-06 16:45:28 +11:00
parent f7e8a53922
commit 049d51e466
6 changed files with 175 additions and 25 deletions

View File

@@ -5,6 +5,12 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IRelationRepository : IReadWriteQueryRepository<int, IRelation>
{
/// <summary>
/// Persist multiple <see cref="IRelation"/> at once
/// </summary>
/// <param name="relations"></param>
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Deletes all relations for a parent for any specified relation type alias
/// </summary>

View File

@@ -853,21 +853,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
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");
var toSave = trackedRelations.Select(rel =>
{
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 (!udiToGuids.TryGetValue(rel.Udi, out var guid))
return null; // This shouldn't happen!
if (!keyToIds.TryGetValue(guid, out var id))
continue; // This shouldn't happen!
if (!keyToIds.TryGetValue(guid, out var id))
return null; // 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));
}
return new Relation(entity.Id, id, relationType);
}).WhereNotNull();
// Save bulk relations
RelationRepository.Save(toSave);
}

View File

@@ -156,6 +156,50 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
public void Save(IEnumerable<IRelation> relations)
{
foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
{
if (hasIdentityGroup.Key)
{
// Do updates, we can't really do a bulk update so this is still a 1 by 1 operation
// however we can bulk populate the object types. It might be possible to bulk update
// with SQL but would be pretty ugly and we're not really too worried about that for perf,
// it's the bulk inserts we care about.
var asArray = hasIdentityGroup.ToArray();
foreach (var relation in hasIdentityGroup)
{
relation.UpdatingEntity();
var dto = RelationFactory.BuildDto(relation);
Database.Update(dto);
}
PopulateObjectTypes(asArray);
}
else
{
// Do bulk inserts
var entitiesAndDtos = hasIdentityGroup.ToDictionary(
r => // key = entity
{
r.AddingEntity();
return r;
},
RelationFactory.BuildDto); // value = DTO
Database.InsertBulk(entitiesAndDtos.Values);
// All dtos now have IDs assigned
foreach (var de in entitiesAndDtos)
{
// re-assign ID to the entity
de.Key.Id = de.Value.Id;
}
PopulateObjectTypes(entitiesAndDtos.Keys.ToArray());
}
}
}
public void DeleteByParent(int parentId, params string[] relationTypeAliases)
{
var subQuery = Sql().Select<RelationDto>(x => x.Id)
@@ -171,19 +215,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
private void PopulateObjectTypes(IRelation entity)
/// <summary>
/// Used to populate the object types after insert/update
/// </summary>
/// <param name="entities"></param>
private void PopulateObjectTypes(params IRelation[] entities)
{
var nodes = Database.Fetch<NodeDto>(Sql().Select<NodeDto>().From<NodeDto>().Where<NodeDto>(x => x.NodeId == entity.ChildId || x.NodeId == entity.ParentId))
var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct();
var nodes = Database.Fetch<NodeDto>(Sql().Select<NodeDto>().From<NodeDto>()
.WhereIn<NodeDto>(x => x.NodeId, entityIds))
.ToDictionary(x => x.NodeId, x => x.NodeObjectType);
if(nodes.TryGetValue(entity.ParentId, out var parentObjectType))
foreach (var e in entities)
{
entity.ParentObjectType = parentObjectType.GetValueOrDefault();
}
if(nodes.TryGetValue(entity.ChildId, out var childObjectType))
{
entity.ChildObjectType = childObjectType.GetValueOrDefault();
if (nodes.TryGetValue(e.ParentId, out var parentObjectType))
{
e.ParentObjectType = parentObjectType.GetValueOrDefault();
}
if (nodes.TryGetValue(e.ChildId, out var childObjectType))
{
e.ChildObjectType = childObjectType.GetValueOrDefault();
}
}
}
}

View File

@@ -286,6 +286,8 @@ namespace Umbraco.Core.Services
/// <param name="relation">Relation to save</param>
void Save(IRelation relation);
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Saves a <see cref="RelationType"/>
/// </summary>

View File

@@ -417,6 +417,24 @@ namespace Umbraco.Core.Services.Implement
}
}
public void Save(IEnumerable<IRelation> relations)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IRelation>(relations);
if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
scope.Complete();
return;
}
_relationRepository.Save(relations);
scope.Complete();
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
}
}
/// <inheritdoc />
public void Save(IRelationType relationType)
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
@@ -37,7 +38,7 @@ namespace Umbraco.Tests.Services
ServiceContext.ContentService.Save(content);
}
for (var i = 0; i < 6; i++)
for (var i = 0; i < 6; i++)
createContentWithMediaRefs(); //create 6 content items referencing the same media
var relations = ServiceContext.RelationService.GetByChildId(m1.Id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList();
@@ -83,7 +84,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Creating()
{
var r = CreateNewRelation("Test", "test");
var r = CreateAndSaveRelation("Test", "test");
Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType);
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
@@ -92,7 +93,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Getting()
{
var r = CreateNewRelation("Test", "test");
var r = CreateAndSaveRelation("Test", "test");
// re-get
r = ServiceContext.RelationService.GetById(r.Id);
@@ -101,7 +102,47 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
}
private IRelation CreateNewRelation(string name, string alias)
[Test]
public void Insert_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var newRelations = CreateRelations(10);
Assert.IsTrue(newRelations.All(x => !x.HasIdentity));
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.HasIdentity));
}
[Test]
public void Update_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var date = DateTime.Now.AddDays(-10);
var newRelations = CreateRelations(10);
foreach (var r in newRelations)
{
r.CreateDate = date;
r.UpdateDate = date;
}
//insert
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == date));
var newDate = DateTime.Now.AddDays(-5);
foreach (var r in newRelations)
r.UpdateDate = newDate;
//update
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == newDate));
}
private IRelation CreateAndSaveRelation(string name, string alias)
{
var rs = ServiceContext.RelationService;
var rt = new RelationType(name, alias, false, null, null);
@@ -124,6 +165,35 @@ namespace Umbraco.Tests.Services
return r;
}
/// <summary>
/// Creates a bunch of content/media items return relation objects for them (unsaved)
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
private IEnumerable<IRelation> CreateRelations(int count)
{
var rs = ServiceContext.RelationService;
var rtName = Guid.NewGuid().ToString();
var rt = new RelationType(rtName, rtName, false, null, null);
rs.Save(rt);
var ct = MockedContentTypes.CreateBasicContentType();
ServiceContext.ContentTypeService.Save(ct);
var mt = MockedContentTypes.CreateImageMediaType("img");
ServiceContext.MediaTypeService.Save(mt);
return Enumerable.Range(1, count).Select(index =>
{
var c1 = MockedContent.CreateBasicContent(ct);
var c2 = MockedMedia.CreateMediaImage(mt, -1);
ServiceContext.ContentService.Save(c1);
ServiceContext.MediaService.Save(c2);
return new Relation(c1.Id, c2.Id, rt);
}).ToList();
}
//TODO: Create a relation for entities of the wrong Entity Type (GUID) based on the Relation Type's defined parent/child object types
}
}