/// <summary> /// Clones all TheThing properties and returns a TheThingStore class /// </summary> /// <param name="iThing">The Thing to clone.</param> /// <param name="ResetBase">Use current time instead of Thing timestamp</param> /// <param name="bUsePropertyTimestamp">Use latest timestamp of any of the properties</param> /// <param name="cloneProperties">Indiciates if current properties and their of the iThing should be added to TheThingStore.PB of only an empty PB should be returned.</param> /// <param name="propertiesToInclude">When cloning, indicates which properties to include. If null, all properties of the thing are included.</param> /// <param name="propertiesToExclude">When cloning, indicates which properties not to return.</param> /// <returns></returns> internal static TheThingStore CloneFromTheThingInternal(ICDEThing iThing, bool ResetBase, bool bUsePropertyTimestamp, bool cloneProperties, IEnumerable <string> propertiesToInclude = null, IEnumerable <string> propertiesToExclude = null) { var filter = new ThePropertyFilter { Properties = propertiesToInclude?.ToList(), PropertiesToExclude = propertiesToExclude?.ToList() }; return(CloneFromTheThingInternal(iThing, ResetBase, bUsePropertyTimestamp, cloneProperties, filter)); }
/// <summary> /// Creates a copy of a TheThingStore with a selectable subset of properties. /// </summary> /// <param name="baseItem">TheThingStore to copy.</param> /// <param name="ResetBase">Creates a new cdeMID and timestamp. Otherwise the item will have the same cdeMID and timestamp as the baseItem. </param> /// <param name="propFilter">Specification of which properties to copy. If null, all properties will be copied.</param> /// <param name="forExternalConsumption">Set a flag that is used by the Historian mechanism internally.</param> /// <returns>The copy of the baseItem TheThingStore</returns> public TheThingStore CloneForThingSnapshot(TheThingStore baseItem, bool ResetBase, ThePropertyFilter propFilter, bool forExternalConsumption) { return(CloneForThingSnapshot(null, baseItem, ResetBase, new TheHistoryParameters(propFilter), forExternalConsumption, out _)); }
/// <summary> /// Clones all TheThing properties and returns a TheThingStore class /// </summary> /// <param name="iThing">The Thing to clone.</param> /// <param name="ResetBase">Use current time instead of Thing timestamp</param> /// <param name="bUsePropertyTimestamp">Use latest timestamp of any of the properties</param> /// <param name="cloneProperties">Indiciates if current properties and their of the iThing should be added to TheThingStore.PB of only an empty PB should be returned.</param> /// <param name="cloneFilter">When cloning, indicates which properties to include. If null, all properties of the thing are included.</param> /// <returns></returns> internal static TheThingStore CloneFromTheThingInternal(ICDEThing iThing, bool ResetBase, bool bUsePropertyTimestamp, bool cloneProperties, ThePropertyFilter cloneFilter) { TheThingStore tThing = new TheThingStore(); if (iThing == null) { return(tThing); } TheThing pThing = iThing.GetBaseThing(); if (pThing != null) { if (ResetBase) { tThing.cdeCTIM = !bUsePropertyTimestamp ? DateTimeOffset.Now : DateTimeOffset.MinValue; tThing.cdeMID = Guid.NewGuid(); } else { tThing.cdeCTIM = pThing.cdeCTIM; tThing.cdeMID = pThing.cdeMID; } tThing.cdeAVA = pThing.cdeAVA; tThing.cdeA = pThing.cdeA; tThing.cdeEXP = pThing.cdeEXP; tThing.cdePRI = pThing.cdePRI; tThing.cdeF = pThing.cdeF; tThing.cdeM = pThing.cdeM; tThing.cdeN = pThing.cdeN; //tThing.cdeO = pThing.cdeO; tThing.cdeO = pThing.cdeMID; //CODE-REVIEW: Since tThing is a "TheThingStore" and is "owned" by the pThing, the cdeO (Owner) property should be updated. Does this break anything for TDS01 etc??? //tThing.OwnerThingMID = pThing.cdeMID; //Alternative we need to add a OwnerThingMID to TheThingStore to be able to segregate retrieval of TheThingStore records from the SQL Server tThing.cdeSEQ = pThing.cdeSEQ; if (cloneProperties) { var propertiesToInclude = pThing.GetMatchingProperties(cloneFilter); int propsIncludeCount = 0; foreach (string key in propertiesToInclude) { try { cdeP prop; if ((prop = pThing.GetProperty(key)) != null) // .MyPropertyBag.TryGetValue(key, out prop)) { //if (prop.CheckAndResetTouchedSinceLastHistorySnapshot() || cloneProperties) // Have to always check and reset, so do this first { tThing.PB[key] = prop.Value; if (bUsePropertyTimestamp) { if (prop.cdeCTIM > tThing.cdeCTIM) { if (tThing.cdeCTIM.Ticks != 0 && tThing.PB.Count > 1) { TheBaseAssets.MySYSLOG.WriteToLog(1, TSM.L(eDEBUG_LEVELS.VERBOSE) ? null : new TSM(eEngineName.ThingService, "Historian timestamp changed by property when cloning for snapshot", eMsgLevel.l6_Debug, $"{prop.Name}: {tThing.cdeCTIM:O} changed to {prop.cdeCTIM:O}. Possible victims: {tThing.PB.Aggregate("", (s, kv) => kv.Key != prop.Name ? $"{s}{kv.Key}," : s)}")); } tThing.cdeCTIM = prop.cdeCTIM; } else if (prop.cdeCTIM != tThing.cdeCTIM) { TheBaseAssets.MySYSLOG.WriteToLog(1, TSM.L(eDEBUG_LEVELS.VERBOSE) ? null : new TSM(eEngineName.ThingService, "Historian timestamp on property not applied when cloning for snapshot", eMsgLevel.l6_Debug, $"{prop.Name}: {prop.cdeCTIM:O} changed to {tThing.cdeCTIM:O}. Possible offenders: {tThing.PB.Aggregate("", (s, kv) => kv.Key != prop.Name ? $"{s}{kv.Key}," : s)}")); } } } } } catch (Exception) { //ignored } propsIncludeCount++; } if (tThing.cdeCTIM == DateTimeOffset.MinValue) { tThing.cdeCTIM = pThing.cdeCTIM; } // We have all properties that are available in the thing as of this time and that are requested: this snapshot can and will now be used as the basis to report unchanged properties tThing.IsFullSnapshot = true; } } return(tThing); }