2828 * ======================================================================*/
2929
3030using System ;
31+ using System . Collections . Concurrent ;
3132using System . Collections . Generic ;
3233using Opc . Ua ;
3334using Opc . Ua . Server ;
@@ -142,6 +143,10 @@ public void Remove(MonitoredItem datachangeItem)
142143 if ( Object . ReferenceEquals ( DataChangeMonitoredItems [ ii ] , datachangeItem ) )
143144 {
144145 DataChangeMonitoredItems . RemoveAt ( ii ) ;
146+
147+ // Remove the cached context for the monitored item
148+ m_contextCache . TryRemove ( datachangeItem . Id , out _ ) ;
149+
145150 break ;
146151 }
147152 }
@@ -271,7 +276,7 @@ public void OnReportEvent(ISystemContext context, NodeState node, IFilterTarget
271276
272277 }
273278 }
274- }
279+ }
275280
276281 /// <summary>
277282 /// Called when the state of a Node changes.
@@ -331,8 +336,15 @@ public void QueueValue(
331336 value . SourceTimestamp = DateTime . MinValue ;
332337 value . StatusCode = StatusCodes . Good ;
333338
339+ ISystemContext contextToUse = context ;
340+
341+ if ( context is ServerSystemContext systemContext )
342+ {
343+ contextToUse = GetOrCreateContext ( systemContext , monitoredItem ) ;
344+ }
345+
334346 ServiceResult error = node . ReadAttribute (
335- context ,
347+ contextToUse ,
336348 monitoredItem . AttributeId ,
337349 monitoredItem . IndexRange ,
338350 monitoredItem . DataEncoding ,
@@ -347,11 +359,53 @@ public void QueueValue(
347359 }
348360 #endregion
349361
362+ #region Private Methods
363+ /// <summary>
364+ /// Gets or creates a cached context for the monitored item.
365+ /// </summary>
366+ /// <param name="monitoredItem">The monitored item.</param>
367+ /// <param name="context">The system context.</param>
368+ /// <returns>The cached or newly created context.</returns>
369+ private ServerSystemContext GetOrCreateContext ( ServerSystemContext context , MonitoredItem monitoredItem )
370+ {
371+ uint monitoredItemId = monitoredItem . Id ;
372+ int currentTicks = HiResClock . TickCount ;
373+
374+ // Check if the context already exists in the cache
375+ if ( m_contextCache . TryGetValue ( monitoredItemId , out var cachedEntry ) )
376+ {
377+ // Check if the session or user identity has changed or the entry has expired
378+ if ( cachedEntry . Context . OperationContext . Session != monitoredItem . Session
379+ || cachedEntry . Context . OperationContext . UserIdentity != monitoredItem . EffectiveIdentity
380+ || ( currentTicks - cachedEntry . CreatedAtTicks ) > m_cacheLifetimeTicks )
381+ {
382+ var updatedContext = context . Copy ( new OperationContext ( monitoredItem ) ) ;
383+ m_contextCache [ monitoredItemId ] = ( updatedContext , currentTicks ) ;
384+
385+ return updatedContext ;
386+ }
387+ // return cached entry
388+ else
389+ {
390+ return cachedEntry . Context ;
391+ }
392+ }
393+
394+ // Create a new context and add it to the cache
395+ var newContext = context . Copy ( new OperationContext ( monitoredItem ) ) ;
396+ m_contextCache . TryAdd ( monitoredItemId , ( newContext , currentTicks ) ) ;
397+
398+ return newContext ;
399+ }
400+ #endregion
401+
350402 #region Private Fields
351403 private CustomNodeManager2 m_nodeManager ;
352404 private NodeState m_node ;
353405 private List < MonitoredItem > m_dataChangeMonitoredItems ;
354406 private List < IEventMonitoredItem > m_eventMonitoredItems ;
407+ private readonly ConcurrentDictionary < uint , ( ServerSystemContext Context , int CreatedAtTicks ) > m_contextCache = new ( ) ;
408+ private readonly int m_cacheLifetimeTicks = ( int ) TimeSpan . FromMinutes ( 5 ) . TotalMilliseconds ;
355409 #endregion
356410 }
357411}
0 commit comments