/// <summary> /// For each DTO that is coming from the data-source, create a clone of the associated cached POCO /// and store this clone in the <see cref="Thing.Revisions"/> dictionary if it is a newer revision. /// For older revisions, store the revision in the cached POCO's <see cref="Thing.Revisions"/> property' /// </summary> /// <param name="dtoThings"> /// the DTO's coming from the data-source /// </param> /// <param name="uncachedOrUpdatedOrNewThingRevisions">A list that contains <see cref="CDP4Common.DTO.Thing"/>s that are </param> /// <remarks> /// If the revision of the DTO is smaller that the revision of the the cached POCO, it is a DTO that represents /// the state of a DTO from the past. It will be added to the cached POCO's <see cref="Thing.Revisions"/> property /// </remarks> private void UpdateThingRevisions(IEnumerable <CDP4Common.DTO.Thing> dtoThings, out IList <CDP4Common.DTO.Thing> uncachedOrUpdatedOrNewThingRevisions) { // create and store a shallow clone of the a current cached Thing var revisionCloneWatch = Stopwatch.StartNew(); uncachedOrUpdatedOrNewThingRevisions = new List <CDP4Common.DTO.Thing>(dtoThings); foreach (var dto in dtoThings) { var cacheKey = new CacheKey(dto.Iid, dto.IterationContainerId); if (this.Cache.TryGetValue(cacheKey, out var currentCachedThing)) { var currentThing = currentCachedThing.Value; if (dto.RevisionNumber > currentThing.RevisionNumber) { if (!currentThing.Revisions.ContainsKey(currentThing.RevisionNumber)) { currentThing.Revisions.Add(currentThing.RevisionNumber, currentThing.Clone(false)); logger.Trace("Revision {0} added to Revisions of {1}:{2}", currentThing.RevisionNumber, currentThing.ClassKind, currentThing.Iid); } else { logger.Trace("Revision {0} of Thing {1}:{2} already exists in the Thing.Revisions cache", currentThing.RevisionNumber, currentThing.ClassKind, currentThing.Iid); } continue; } if (dto.RevisionNumber == currentThing.RevisionNumber) { logger.Trace("A DTO with revision {0} equal to the revision of the existing POCO {1}:{2}:{3} has been identified; The data-source has sent a revision of an object that is already present in the cache", dto.RevisionNumber, currentThing.ClassKind, currentThing.CacheKey.Thing, currentThing.CacheKey.Iteration); continue; } if (dto.RevisionNumber < currentThing.RevisionNumber) { uncachedOrUpdatedOrNewThingRevisions.Remove(dto); //Add the found DTO to the currentThing's Revisions property var poco = dto.InstantiatePoco(this.Cache, this.IDalUri); PocoThingFactory.ResolveDependencies(dto, poco); if (!currentThing.Revisions.ContainsKey(dto.RevisionNumber)) { currentThing.Revisions.Add(dto.RevisionNumber, poco); logger.Trace("Revision {0} added to Revisions of {1}:{2}", dto.RevisionNumber, currentThing.ClassKind, currentThing.Iid); } else { logger.Trace("Revision {0} of Thing {1}:{2} already exists in the Thing.Revisions cache", currentThing.RevisionNumber, currentThing.ClassKind, currentThing.Iid); } } } } logger.Info("Updating Thing.Revisions took {0} [ms]", revisionCloneWatch.ElapsedMilliseconds); }
/// <summary> /// Synchronize the Cache give an IEnumerable of DTO <see cref="Thing"/> /// </summary> /// <param name="dtoThings"> /// the DTOs /// </param> /// <param name="activeMessageBus"> /// An optional value indicating whether the <see cref="CDPMessageBus"/> should publish <see cref="ObjectChangedEvent"/> or not. /// The default value is true /// </param> /// <returns> /// The <see cref="Task"/> that can be awaited. /// </returns> public async Task Synchronize(IEnumerable <CDP4Common.DTO.Thing> dtoThings, bool activeMessageBus = true) { if (dtoThings == null) { throw new ArgumentNullException(nameof(dtoThings), $"The {nameof(dtoThings)} may not be null"); } await this.threadLock.WaitAsync().ConfigureAwait(false); try { var synchronizeStopWatch = Stopwatch.StartNew(); logger.Info("Start Synchronization of {0}", this.IDalUri); var existentGuid = this.Cache.Select( x => new Tuple <CacheKey, int>(x.Value.Value.CacheKey, x.Value.Value.RevisionNumber)) .ToList(); this.CheckPartitionDependentContainmentContainerIds(dtoThings); this.UpdateThingRevisions(dtoThings, out var uncachedOrUpdatedOrNewerThingRevisions); this.thingsMarkedForDeletion = new List <Thing>(); logger.Trace("Starting Clean-up Unused references"); var startwatch = Stopwatch.StartNew(); this.DtoThingToUpdate = dtoThings.ToList(); // Add the unresolved thing to the things to resolved in case it is possible to fully resolve them with the current update // an example would be Citation contained by SiteDirectory where its Source is contained by a Rdl that is not loaded yet var unresolvedThingToUpdate = this.unresolvedDtos.Where(x => !this.DtoThingToUpdate.Select(y => y.Iid).Contains(x.Iid)); this.DtoThingToUpdate.AddRange(unresolvedThingToUpdate); this.unresolvedDtos.Clear(); if (!this.Cache.IsEmpty) { // marks things for deletion this.ComputeThingsToRemoveInUpdatedThings(); startwatch.Stop(); logger.Trace("Clean up Unused references took {0} [ms]", startwatch.ElapsedMilliseconds); } logger.Trace("Start Updating cache"); startwatch = Stopwatch.StartNew(); this.AddOrUpdateTheCache(uncachedOrUpdatedOrNewerThingRevisions); startwatch.Stop(); logger.Trace("Updating cache took {0} [ms]", startwatch.ElapsedMilliseconds); logger.Trace("Start Resolving properties"); startwatch = Stopwatch.StartNew(); PocoThingFactory.ResolveDependencies(uncachedOrUpdatedOrNewerThingRevisions, this.Cache); startwatch.Stop(); logger.Trace("Resolving properties took {0} [ms]", startwatch.ElapsedMilliseconds); // validate POCO's logger.Trace("Start validating Things"); startwatch = Stopwatch.StartNew(); foreach (var dtoThing in this.DtoThingToUpdate) { var cacheKey = new CacheKey(dtoThing.Iid, dtoThing.IterationContainerId); var succeed = this.Cache.TryGetValue(cacheKey, out var updatedLazyThing); if (succeed) { var thingObject = updatedLazyThing.Value; thingObject.ValidatePoco(); // add to the list of unresolved dtos if there is an error if (thingObject.ValidationErrors.Any()) { this.unresolvedDtos.Add(dtoThing); } } } startwatch.Stop(); logger.Trace("Validating {0} Things took {1} [ms]", this.DtoThingToUpdate.Count, startwatch.ElapsedMilliseconds); // message added and updated POCO's if (activeMessageBus) { logger.Trace("Start Messaging"); startwatch = Stopwatch.StartNew(); var messageCounter = 0; foreach (var dtoThing in uncachedOrUpdatedOrNewerThingRevisions) { var cacheKey = new CacheKey(dtoThing.Iid, dtoThing.IterationContainerId); var succeed = this.Cache.TryGetValue(cacheKey, out var updatedLazyThing); if (succeed) { var thingObject = updatedLazyThing.Value; var cacheId = new CacheKey(dtoThing.Iid, dtoThing.IterationContainerId); if (!existentGuid.Select(x => x.Item1).Contains(cacheId)) { CDPMessageBus.Current.SendObjectChangeEvent(thingObject, EventKind.Added); messageCounter++; } else { var cacheThingRevisionNumber = existentGuid.Single(x => x.Item1.Equals(cacheId)).Item2; if (dtoThing.RevisionNumber > cacheThingRevisionNumber) { // send event if revision number has increased from the old cached version CDPMessageBus.Current.SendObjectChangeEvent(thingObject, EventKind.Updated); messageCounter++; } else if (dtoThing.RevisionNumber < cacheThingRevisionNumber) { if (this.Cache.TryGetValue(cacheId, out var cacheThing)) { // send event if revision number is lower. That means that the original cached item was changed (revision was added!) CDPMessageBus.Current.SendObjectChangeEvent(cacheThing.Value, EventKind.Updated); CDPMessageBus.Current.SendObjectChangeEvent(cacheThing.Value, EventKind.Updated); messageCounter++; } } } } } startwatch.Stop(); logger.Trace("Messaging {0} Things took {1} [ms]", messageCounter, startwatch.ElapsedMilliseconds); } logger.Trace("Start Deleting things"); startwatch = Stopwatch.StartNew(); foreach (var markedThing in this.thingsMarkedForDeletion.Where(x => x.ChangeKind == ChangeKind.Delete)) { this.RemoveThingFromCache(markedThing); } var deletedIterationSetups = this.DtoThingToUpdate.OfType <CDP4Common.DTO.IterationSetup>().Where(x => x.IsDeleted).ToList(); var deletedModelSetups = this.thingsMarkedForDeletion.OfType <EngineeringModelSetup>().ToList(); this.thingsMarkedForDeletion.Clear(); if (deletedIterationSetups.Any()) { foreach (var deletedIterationSetup in deletedIterationSetups) { this.MarkAndDelete(deletedIterationSetup.IterationIid); } } if (deletedModelSetups.Any()) { foreach (var deletedModelSetup in deletedModelSetups) { this.MarkAndDelete(deletedModelSetup.EngineeringModelIid); } } startwatch.Stop(); logger.Trace("Deleting things took {0} [ms]", startwatch.ElapsedMilliseconds); this.DtoThingToUpdate.Clear(); if (this.siteDirectory == null) { var keyvaluepair = this.Cache.Single(item => item.Value.Value.ClassKind == ClassKind.SiteDirectory); this.siteDirectory = (SiteDirectory)keyvaluepair.Value.Value; } logger.Info("Finish Synchronization of {0} in {1} [ms]", this.IDalUri, synchronizeStopWatch.ElapsedMilliseconds); } catch (Exception e) { logger.Error(e); } finally { this.threadLock.Release(); logger.Trace("Assembler thread released"); } }