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); + } + } + } } }