diff --git a/CDP4WebServices.API/Modules/10-25/EngineeringModelApi.cs b/CDP4WebServices.API/Modules/10-25/EngineeringModelApi.cs
index 214f9f77..e4ead125 100644
--- a/CDP4WebServices.API/Modules/10-25/EngineeringModelApi.cs
+++ b/CDP4WebServices.API/Modules/10-25/EngineeringModelApi.cs
@@ -456,6 +456,7 @@ protected override Response PostResponseData(dynamic routeParams)
{
transaction?.Dispose();
connection?.Dispose();
+ sw.Stop();
}
}
diff --git a/CDP4WebServices.API/Services/ChangeLog/ChangeLogService.cs b/CDP4WebServices.API/Services/ChangeLog/ChangeLogService.cs
index 51e0c8fd..ad5d9b88 100644
--- a/CDP4WebServices.API/Services/ChangeLog/ChangeLogService.cs
+++ b/CDP4WebServices.API/Services/ChangeLog/ChangeLogService.cs
@@ -182,6 +182,13 @@ public bool TryAppendModelChangeLogData(NpgsqlTransaction transaction, string pa
{
var sw = Stopwatch.StartNew();
Logger.Info("Starting to append changelog data");
+
+ var isCachedDtoReadEnabled = this.TransactionManager.IsCachedDtoReadEnabled(transaction);
+
+ if (!isCachedDtoReadEnabled)
+ {
+ this.TransactionManager.SetCachedDtoReadEnabled(true);
+ }
var isFullAccessEnabled = this.TransactionManager.IsFullAccessEnabled();
var result = false;
@@ -306,6 +313,8 @@ public bool TryAppendModelChangeLogData(NpgsqlTransaction transaction, string pa
operationData.Update.Add(modelLogEntryClasslessDTO);
}
+ // New things that need to be read that are not yet in cache at this moment in time
+ this.TransactionManager.SetCachedDtoReadEnabled(false);
this.OperationProcessor.Process(operationData, transaction, partition);
result = true;
@@ -322,8 +331,15 @@ public bool TryAppendModelChangeLogData(NpgsqlTransaction transaction, string pa
{
this.TransactionManager.SetFullAccessState(false);
}
+
+ if (!isCachedDtoReadEnabled && this.TransactionManager.IsCachedDtoReadEnabled(transaction))
+ {
+ this.TransactionManager.SetCachedDtoReadEnabled(false);
+ }
}
+ sw.Stop();
+
Logger.Info($"Finished appending to changelog data in {sw.ElapsedMilliseconds} [ms]");
return result;
diff --git a/CDP4WebServices.API/Services/Operations/OperationProcessor.cs b/CDP4WebServices.API/Services/Operations/OperationProcessor.cs
index 8f8f9804..020df2ff 100644
--- a/CDP4WebServices.API/Services/Operations/OperationProcessor.cs
+++ b/CDP4WebServices.API/Services/Operations/OperationProcessor.cs
@@ -33,7 +33,6 @@ namespace CDP4WebServices.API.Services.Operations
using CDP4Orm.Dao;
using CDP4Orm.Dao.Resolve;
-
using CDP4WebServices.API.Services.Authorization;
using CDP4WebServices.API.Services.Operations.SideEffects;
@@ -44,6 +43,7 @@ namespace CDP4WebServices.API.Services.Operations
using System;
using System.Collections;
using System.Collections.Generic;
+ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
@@ -818,6 +818,33 @@ private void DeletePersistedItem(NpgsqlTransaction transaction, string partition
service.DeleteConcept(transaction, partition, thing);
}
+ ///
+ /// Try and get persisted items from the data store.
+ ///
+ ///
+ /// The current transaction to the database.
+ ///
+ ///
+ /// The database partition (schema) where the requested resource will be stored.
+ ///
+ ///
+ /// The service instance for the requested type.
+ ///
+ ///
+ /// The id of the item to retrieve.
+ ///
+ ///
+ /// The retrieved or type instances
+ ///
+ ///
+ /// The security Context used for permission checking.
+ ///
+ private IEnumerable GetPersistedItems(NpgsqlTransaction transaction, string partition, IPersistService service, IEnumerable iids, ISecurityContext securityContext)
+ {
+ return service.GetShallow(
+ transaction, partition, iids, securityContext);
+ }
+
///
/// Try and get persisted item from the data store.
///
@@ -960,55 +987,71 @@ private void ApplyCreateOperations(CdpPostOperation operation, NpgsqlTransaction
{
// re-order create
this.ReorderCreateOrder(operation);
- foreach (var createInfo in operation.Create.Select(x => x.GetInfoPlaceholder()))
+
+ if (operation.Create.Any())
{
- var service = this.ServiceProvider.MapToPersitableService(createInfo.TypeName);
+ var operationInfoPlaceholders = operation.Create.Select(x => x.GetInfoPlaceholder());
var securityContext = new RequestSecurityContext { ContainerReadAllowed = true };
-
securityContext.Credentials = this.RequestUtils.Context.AuthenticatedCredentials;
- var resolvedInfo = this.operationThingCache[createInfo];
-
- // check that item doen not exist:
- var persistedItem = this.GetPersistedItem(transaction, resolvedInfo.Partition, service, createInfo.Iid, securityContext);
+ var typeGroups = operationInfoPlaceholders.GroupBy(x => x.TypeName).Select(x => new {TypeName = x.Key, Things = x.ToList()});
- if (persistedItem != null)
+ foreach (var typeGroup in typeGroups)
{
- throw new InvalidOperationException(
- string.Format("Item '{0}' with Iid: '{1}' already exists", createInfo.TypeName, createInfo.Iid));
- }
+ var partitions = this.operationThingCache.Where(x => typeGroup.Things.Contains(x.Key)).Select(x => x.Value.Partition).Distinct();
+ foreach (var partition in partitions)
+ {
+ var service = this.ServiceProvider.MapToPersitableService(typeGroup.TypeName);
+ var typeGroupIids = typeGroup.Things.Select(x => x.Iid);
+
+ var persistedItems = this.GetPersistedItems(transaction, partition, service, typeGroupIids, securityContext);
- // get the (cached) containment information for this create request
- var resolvedContainerInfo = this.GetContainerInfo(resolvedInfo.Thing);
+ foreach (var createInfo in typeGroup.Things)
+ {
+ var resolvedInfo = this.operationThingCache[createInfo];
- // keep a copy of the orginal thing to pass to the after create hook
- var originalThing = resolvedInfo.Thing.DeepClone();
+ var persistedItem = persistedItems.FirstOrDefault(x => x.Iid.Equals(createInfo.Iid));
- if (this.operationOriginalThingCache.All(x => x.Iid != originalThing.Iid))
- {
- this.operationOriginalThingCache.Add(originalThing);
- }
+ if (persistedItem != null)
+ {
+ throw new InvalidOperationException(
+ string.Format("Item '{0}' with Iid: '{1}' already exists", createInfo.TypeName, createInfo.Iid));
+ }
- // call before create hook
- if (!this.OperationSideEffectProcessor.BeforeCreate(resolvedInfo.Thing, resolvedContainerInfo.Thing, transaction, resolvedInfo.Partition, securityContext))
- {
- Logger.Warn("Skipping create operation of thing {0} with id {1} as a consequence of the side-effect.", createInfo.TypeName, createInfo.Iid);
- continue;
- }
+ // get the (cached) containment information for this create request
+ var resolvedContainerInfo = this.GetContainerInfo(resolvedInfo.Thing);
- if (resolvedInfo.ContainerInfo.ContainmentSequence != -1)
- {
- service.CreateConcept(transaction, resolvedInfo.Partition, resolvedInfo.Thing, resolvedContainerInfo.Thing, resolvedInfo.ContainerInfo.ContainmentSequence);
- }
- else
- {
- service.CreateConcept(transaction, resolvedInfo.Partition, resolvedInfo.Thing, resolvedContainerInfo.Thing);
- }
+ // keep a copy of the orginal thing to pass to the after create hook
+ var originalThing = resolvedInfo.Thing.DeepClone();
- var createdItem = this.GetPersistedItem(transaction, resolvedInfo.Partition, service, createInfo.Iid, securityContext);
+ if (this.operationOriginalThingCache.All(x => x.Iid != originalThing.Iid))
+ {
+ this.operationOriginalThingCache.Add(originalThing);
+ }
+
+ // call before create hook
+ if (resolvedInfo.Thing is ParameterValueSet || !this.OperationSideEffectProcessor.BeforeCreate(resolvedInfo.Thing, resolvedContainerInfo.Thing, transaction, resolvedInfo.Partition, securityContext))
+ {
+ Logger.Warn("Skipping create operation of thing {0} with id {1} as a consequence of the side-effect.", createInfo.TypeName, createInfo.Iid);
+ continue;
+ }
+
+ if (resolvedInfo.ContainerInfo.ContainmentSequence != -1)
+ {
+ service.CreateConcept(transaction, resolvedInfo.Partition, resolvedInfo.Thing, resolvedContainerInfo.Thing, resolvedInfo.ContainerInfo.ContainmentSequence);
+ }
+ else
+ {
+ service.CreateConcept(transaction, resolvedInfo.Partition, resolvedInfo.Thing, resolvedContainerInfo.Thing);
+ }
+
+ var createdItem = this.GetPersistedItem(transaction, resolvedInfo.Partition, service, createInfo.Iid, securityContext);
- // call after create hook
- this.OperationSideEffectProcessor.AfterCreate(createdItem, resolvedContainerInfo.Thing, originalThing, transaction, resolvedInfo.Partition, securityContext);
+ // call after create hook
+ this.OperationSideEffectProcessor.AfterCreate(createdItem, resolvedContainerInfo.Thing, originalThing, transaction, resolvedInfo.Partition, securityContext);
+ }
+ }
+ }
}
}