/// <summary> /// Creates a copy of a TheThingStore with a selectable subset of properties. /// </summary> /// <param name="thingWithMeta">An optional thing with meta information like Sensor or Config property information.</param> /// <param name="baseItem">Optional TheThingStore with additional properties to copy into the clone.</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> /// <param name="bUpdated">Returns true if an of the baseItem properties overwrote properties in the TheThingStore.</param> /// <returns>The copy of the baseItem TheThingStore</returns> internal TheThingStore CloneForThingSnapshot(TheThing thingWithMeta, TheThingStore baseItem, bool ResetBase, TheHistoryParameters propFilter, bool forExternalConsumption, out bool bUpdated) { TheThingStore tThing = new TheThingStore(); if (ResetBase) { tThing.cdeCTIM = DateTimeOffset.Now; tThing.cdeMID = Guid.NewGuid(); } else { tThing.cdeCTIM = cdeCTIM; tThing.cdeMID = cdeMID; } tThing.cdeAVA = cdeAVA; tThing.cdeA = cdeA; if (baseItem != null && baseItem.cdeEXP != 0 && cdeEXP == 0) { tThing.cdeEXP = baseItem.cdeEXP; } else { tThing.cdeEXP = cdeEXP; } tThing.cdePRI = cdePRI; tThing.cdeF = cdeF; tThing.cdeM = cdeM; tThing.cdeN = cdeN; tThing.cdeO = cdeO; tThing.cdeSEQ = cdeSEQ; IEnumerable <string> propertiesToInclude = null; if (propFilter?.Properties != null || propFilter.FilterToSensorProperties == true || propFilter.FilterToConfigProperties == true) { propertiesToInclude = thingWithMeta?.GetMatchingProperties(propFilter); if (propertiesToInclude != null) { if (propFilter.ComputeMax) { propertiesToInclude = propertiesToInclude.Concat(propertiesToInclude.Select(propName => $"[{propName}].Max")); } if (propFilter.ComputeAvg) { propertiesToInclude = propertiesToInclude.Concat(propertiesToInclude.Select(propName => $"[{propName}].Avg")); } if (propFilter.ComputeMin) { propertiesToInclude = propertiesToInclude.Concat(propertiesToInclude.Select(propName => $"[{propName}].Min")); } if (propFilter.ComputeN) { propertiesToInclude = propertiesToInclude.Concat(propertiesToInclude.Select(propName => $"[{propName}].N")); } } } if (propertiesToInclude == null) { propertiesToInclude = baseItem != null?baseItem.PB.Keys.Union(PB.Keys) : PB.Keys; if (propFilter?.PropertiesToExclude?.Any() == true) { var tempList = new List <string>(propertiesToInclude); foreach (var prop in propFilter.PropertiesToExclude) { tempList.Remove(prop); } propertiesToInclude = tempList; } } bUpdated = false; foreach (string key in propertiesToInclude) { if (PB.ContainsKey(key)) { bUpdated = true; tThing.PB[key] = PB[key]; } else if (baseItem != null && baseItem.PB.ContainsKey(key)) { try { tThing.PB[key] = baseItem.PB[key]; } catch (Exception) { //ignored } } } if (forExternalConsumption) { tThing.IsFullSnapshot = false; tThing.IsInitialValue = false; } else { if (IsFullSnapshot || (baseItem != null && baseItem.IsFullSnapshot)) { tThing.IsFullSnapshot = true; } } if (!bUpdated && baseItem != null) { return(null); // all properties came from the baseItem, which means the thing update didn't have any matching property changes } return(tThing); }
/// <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); }