# Werte gebündelt verarbeiten

> Anleitung: Tausende Werte mit einem Aufruf lesen, schreiben und abonnieren: wie das SDK große Anfragen an die Grenzen des Servers anpasst und was sich wann lohnt.

**Beispielanwendungen:**

- Client.BulkChanges ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Client.BulkChanges), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Client.BulkChanges)): Abonniert sehr viele Nodes und verarbeitet ihre Datenänderungen in einem eigenen Thread.
- Server.BulkChanges ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Server.BulkChanges), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Server.BulkChanges)): Ändert die Werte sehr vieler Nodes gleichzeitig in einem festen Intervall.

Jeder Aufruf von `ReadNode` oder `WriteNode` ist eine eigene Anfrage an den Server mit eigener Netzwerklaufzeit. Wer viele Werte in einer Schleife einzeln liest, verliert dadurch schnell Sekunden. Die Methoden `ReadNodes`, `WriteNodes` und `SubscribeNodes` nehmen stattdessen beliebig viele Befehle in einem Aufruf an. Das SDK teilt sie selbst so auf, dass der Server sie annimmt, und fügt die Antworten in der Reihenfolge der Befehle wieder zusammen.

## Viele Werte mit einem Aufruf lesen

Beteiligte Typen: `OpcClient`, `OpcReadNode`, `OpcValue`

Die Ergebnisse stehen in derselben Reihenfolge wie die Befehle. Werten Sie den Status jedes Werts einzeln aus. Ein ungültiger Node bricht den Aufruf nicht ab:

```csharp
var commands = new OpcReadNode[5000];
 
for (int index = 0; index < commands.Length; index++)
    commands[index] = new OpcReadNode("ns=2;s=Sensors/Sensor" + (index + 1));
 
IEnumerable<OpcValue> values = client.ReadNodes(commands);

```

```vbnet
Dim commands = New OpcReadNode(4999) {}

For index As Integer = 0 To commands.Length - 1
    commands(index) = New OpcReadNode("ns=2;s=Sensors/Sensor" & (index + 1))
Next

Dim values As IEnumerable(Of OpcValue) = client.ReadNodes(commands)
```

## Viele Werte mit einem Aufruf schreiben

Beteiligte Typen: `OpcClient`, `OpcWriteNode`, `OpcStatus`, `OpcStatusCollection`

Auch beim Schreiben gibt es je Befehl ein Ergebnis. Über den Index ordnen Sie jedem Ergebnis den zugehörigen Befehl zu:

```csharp
var commands = new OpcWriteNode[5000];
 
for (int index = 0; index < commands.Length; index++)
    commands[index] = new OpcWriteNode("ns=2;s=Setpoints/Setpoint" + (index + 1), value: 0.0);
 
OpcStatusCollection results = client.WriteNodes(commands);
 
for (int index = 0; index < results.Count; index++) {
    if (results[index].IsBad)
        Console.WriteLine("{0}: {1}", commands[index].NodeId, results[index].Description);
}

```

```vbnet
Dim commands = New OpcWriteNode(4999) {}

For index As Integer = 0 To commands.Length - 1
    commands(index) = New OpcWriteNode("ns=2;s=Setpoints/Setpoint" & (index + 1), value:=0.0)
Next

Dim results As OpcStatusCollection = client.WriteNodes(commands)

For index As Integer = 0 To results.Count - 1
    If results(index).IsBad Then
        Console.WriteLine("{0}: {1}", commands(index).NodeId, results(index).Description)
    End If
Next
```

## Wie das SDK große Anfragen aufteilt

Beteiligte Typen: `OpcClient`, `OpcClientServices`

Server begrenzen, wie viele Nodes eine einzelne Anfrage enthalten darf, und melden diese Grenzen unter `Server/ServerCapabilities/OperationLimits`. Wenn ein Aufruf mehr als einen Befehl enthält, geht der Client so vor:

1. Beim ersten solchen Aufruf liest er die Grenze des Servers für den jeweiligen Dienst (`MaxNodesPerRead`, `MaxNodesPerWrite`, `MaxNodesPerBrowse` und `MaxNodesPerTranslateBrowsePathsToNodeIds`). Wenn der Server keine Grenze meldet (0), verwendet der Client 1000.
2. Wenn für den Dienst eine eigene Grenze gesetzt ist, gilt der kleinere der beiden Werte.
3. Der Client merkt sich den Wert und teilt alle weiteren Aufrufe danach auf mehrere Anfragen auf. Die Antworten fügt er in der Reihenfolge der Befehle zusammen.

Das gilt für `ReadNodes`, `WriteNodes`, das Browsen und `TranslatePaths`. Eine eigene, niedrigere Grenze ist sinnvoll, wenn einzelne Anfragen kürzer dauern sollen oder der Server trotz seiner gemeldeten Grenze an Antwortgrößen scheitert:

```csharp
client.Services.ReadNodes.MaxNodesPerRead = 500;
client.Services.WriteNodes.MaxNodesPerWrite = 500;
client.Services.Browse.MaxNodesPerBrowse = 100;
```

```vbnet
client.Services.ReadNodes.MaxNodesPerRead = 500
client.Services.WriteNodes.MaxNodesPerWrite = 500
client.Services.Browse.MaxNodesPerBrowse = 100
```

Welche Grenze der Client tatsächlich verwendet, zeigt die zugehörige Eigenschaft mit dem Präfix `Used`. Sie ist `null`, solange noch kein Aufruf mit mehreren Befehlen stattgefunden hat:

```csharp
uint? nodesPerRead = client.Services.ReadNodes.UsedMaxNodesPerRead;
```

```vbnet
Dim nodesPerRead As UInteger? = client.Services.ReadNodes.UsedMaxNodesPerRead
```

Unabhängig von der Anzahl der Nodes begrenzt der Client die Größe der übertragenen Daten. Wie sich diese Grenzen erhöhen lassen, beschreibt der Abschnitt [Grenzen der Datenübertragung](https://www.traeger.de/docs/development/opcua/client/configuration#grenzen-der-datenubertragung).

## Viele Nodes abonnieren

Beteiligte Typen: `OpcClient`, `OpcSubscribeDataChange`, `OpcSubscription`, `OpcMonitoredItem`

Wenn sich die Werte im Verhältnis zum Leseintervall selten ändern, ist eine Subscription günstiger als zyklisches Lesen, denn der Server meldet nur Änderungen. `SubscribeNodes` legt alle MonitoredItems in einer Subscription an. Im Handler ist der `sender` das MonitoredItem. Über dessen NodeId ordnen Sie die Benachrichtigung zu:

```csharp
var commands = new OpcSubscribeDataChange[1000];
 
for (int index = 0; index < commands.Length; index++) {
    commands[index] = new OpcSubscribeDataChange(
            "ns=2;s=Sensors/Sensor" + (index + 1),
            HandleDataChanged);
}
 
OpcSubscription subscription = client.SubscribeNodes(commands);
...
private static void HandleDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    OpcMonitoredItem item = (OpcMonitoredItem)sender;
    Console.WriteLine("{0}: {1}", item.NodeId, e.Item.Value);
}
```

```vbnet
Dim commands = New OpcSubscribeDataChange(999) {}

For index As Integer = 0 To commands.Length - 1
    commands(index) = New OpcSubscribeDataChange( _
            "ns=2;s=Sensors/Sensor" & (index + 1), _
            AddressOf HandleDataChanged)
Next

Dim subscription As OpcSubscription = client.SubscribeNodes(commands)
...
Private Shared Sub HandleDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    Dim item As OpcMonitoredItem = CType(sender, OpcMonitoredItem)
    Console.WriteLine("{0}: {1}", item.NodeId, e.Item.Value)
End Sub

```

## Die passende Zugriffsart wählen

| Situation | Empfehlung |
|---|---|
| Werte werden einmalig oder selten benötigt | `ReadNodes` mit allen Nodes in einem Aufruf |
| dieselben Nodes werden zyklisch gelesen oder geschrieben | Nodes registrieren und mit den registrierten Identifiern `ReadNodes` oder `WriteNodes` aufrufen, siehe [Häufig gelesene Nodes registrieren](https://www.traeger.de/docs/development/opcua/client/read-write-values#haufig-gelesene-nodes-registrieren) |
| Werte ändern sich selten, die Anwendung reagiert auf Änderungen | Subscription mit allen Nodes über `SubscribeNodes`, siehe [Subscriptions](https://www.traeger.de/docs/development/opcua/client/subscriptions) |
| sehr große Einzelwerte (Binärdaten) | Werte als Stream lesen, siehe [Große Binärwerte als Stream](https://www.traeger.de/docs/development/opcua/client/node-types#gro%C3%9Fe-binarwerte-als-stream) |
| Nodes sind nur über ihren Pfad bekannt | einmal mit `TranslatePaths` auflösen, siehe [Nodes über Pfade finden](https://www.traeger.de/docs/development/opcua/client/browse#nodes-uber-pfade-finden) |
