Adds ability to bulk insert relations and adds tests
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user