# Data types at runtime

> How-to guide: Define structures and enumerations at runtime, publish them in the address space, and provide their values without your own .NET types.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/server/dynamic-data-types)

Types used: `OpcServer`, `OpcStruct`, `OpcStructField`, `OpcEnum`, `OpcEnumField`, `OpcDataTypeNode`, `OpcDataObject`

You usually define custom data types through .NET types and attributes. The [Provide data types](https://www.traeger.de/en/docs/development/opcua/server/node-types#provide-data-types) section describes this. If the data types are only known at runtime, for example because they come from a configuration or a connected PLC, define them through the `OpcStruct` (structures) and `OpcEnum` (enumerations) classes instead. The server provides the values of such data types as instances of `OpcDataObject`, without any .NET types of your own.

## Enable dynamic data types

To let the server encode values of dynamically defined data types, enable the server's `UseDynamic` property. By default, it follows `OpcAutomatism.UseDynamic`, which is disabled:

```csharp
server.UseDynamic = true;
```

```vbnet
server.UseDynamic = True
```

## Define structures

To define a structure, call the `DefineType` method of the `OpcStruct` class. It takes the `OpcNodeId` and the name of the data type as well as its fields as instances of `OpcStructField`. Each field consists of its name, the `OpcNodeId` of its data type (for example from the `OpcDataTypes` class), and an optional description. The `AsArray` method turns a field into an array, the `AsOptional` method into an optional field:

```csharp
var machineDataTypeId = new OpcNodeId("MachineData", namespaceIndex: 2);
 
var machineDataType = OpcStruct.DefineType(
        machineDataTypeId,
        new OpcName("MachineData", namespaceIndex: 2),
        new OpcStructField("Speed", OpcDataTypes.Int32),
        new OpcStructField("JobName", OpcDataTypes.String),
        new OpcStructField("Temperatures", OpcDataTypes.Double).AsArray(),
        new OpcStructField("Comment", OpcDataTypes.String, description: "Optional comment.")
                .AsOptional());
```

```vbnet
Dim machineDataTypeId = New OpcNodeId("MachineData", namespaceIndex:=2)

Dim machineDataType = OpcStruct.DefineType( _
        machineDataTypeId, _
        New OpcName("MachineData", namespaceIndex:=2), _
        New OpcStructField("Speed", OpcDataTypes.Int32), _
        New OpcStructField("JobName", OpcDataTypes.String), _
        New OpcStructField("Temperatures", OpcDataTypes.Double).AsArray(), _
        New OpcStructField("Comment", OpcDataTypes.String, description:="Optional comment.") _
                .AsOptional())
```

If a structure should extend another one, define it through the `DefineSubType` method and specify the base type:

```csharp
var extendedDataType = OpcStruct.DefineSubType(
        machineDataTypeId,
        new OpcNodeId("ExtendedMachineData", namespaceIndex: 2),
        new OpcName("ExtendedMachineData", namespaceIndex: 2),
        new OpcStructField("Operator", OpcDataTypes.String));
```

```vbnet
Dim extendedDataType = OpcStruct.DefineSubType( _
        machineDataTypeId, _
        New OpcNodeId("ExtendedMachineData", namespaceIndex:=2), _
        New OpcName("ExtendedMachineData", namespaceIndex:=2), _
        New OpcStructField("Operator", OpcDataTypes.String))
```

## Define enumerations

You define an enumeration through the `OpcEnum` class in one of two ways:

- With the `DefineStrings` method as a list of names (EnumStrings). The value of an entry is its index.
- With the `DefineValues` method as a list of name-value pairs (EnumValues). The values do not have to be consecutive, and each entry can have a description.

```csharp
var jobKindId = new OpcNodeId("JobKind", namespaceIndex: 2);
 
var jobKind = OpcEnum.DefineStrings(
        jobKindId,
        new OpcName("JobKind", namespaceIndex: 2),
        new OpcEnumField("Production"),
        new OpcEnumField("Maintenance"));
 
var errorCode = OpcEnum.DefineValues(
        new OpcNodeId("ErrorCode", namespaceIndex: 2),
        new OpcName("ErrorCode", namespaceIndex: 2),
        new OpcEnumField("None", value: 0),
        new OpcEnumField("Overheated", value: 10, description: "The temperature exceeded the limit."),
        new OpcEnumField("OutOfMaterial", value: 20, description: "The material supply is empty."));
```

```vbnet
Dim jobKindId = New OpcNodeId("JobKind", namespaceIndex:=2)

Dim jobKind = OpcEnum.DefineStrings( _
        jobKindId, _
        New OpcName("JobKind", namespaceIndex:=2), _
        New OpcEnumField("Production"), _
        New OpcEnumField("Maintenance"))

Dim errorCode = OpcEnum.DefineValues( _
        New OpcNodeId("ErrorCode", namespaceIndex:=2), _
        New OpcName("ErrorCode", namespaceIndex:=2), _
        New OpcEnumField("None", value:=0), _
        New OpcEnumField("Overheated", value:=10, description:="The temperature exceeded the limit."), _
        New OpcEnumField("OutOfMaterial", value:=20, description:="The material supply is empty."))
```

## Create variables with dynamic data types

You create variables that use a dynamically defined data type in the node manager as usual. The `DataTypeId` property sets their data type:

```csharp
// MyNodeManager.CreateNodes(...)
var machineDataNode = new OpcDataVariableNode(machineNode, "Data");
machineDataNode.DataTypeId = machineDataTypeId;
 
var jobKindNode = new OpcDataVariableNode(machineNode, "JobKind");
jobKindNode.DataTypeId = jobKindId;
```

```vbnet
'MyNodeManager.CreateNodes(...)
Dim machineDataNode = New OpcDataVariableNode(machineNode, "Data")
machineDataNode.DataTypeId = machineDataTypeId

Dim jobKindNode = New OpcDataVariableNode(machineNode, "JobKind")
jobKindNode.DataTypeId = jobKindId
```

## Publish data types

The server publishes the data types through data type nodes. The `Create` method of the `OpcDataTypeNode` class creates these nodes from the respective definition. Because the server resolves the data types of the fields to create these nodes, add them only after the server has started, through the `AddNode` method of the node manager:

```csharp
server.Start();
 
// The data type nodes are added after the server has been started.
nodeManager.AddNode(OpcDataTypeNode.Create(machineDataType));
nodeManager.AddNode(OpcDataTypeNode.Create(jobKind));
nodeManager.AddNode(OpcDataTypeNode.Create(errorCode));
```

```vbnet
server.Start()

'The data type nodes are added after the server has been started.
nodeManager.AddNode(OpcDataTypeNode.Create(machineDataType))
nodeManager.AddNode(OpcDataTypeNode.Create(jobKind))
nodeManager.AddNode(OpcDataTypeNode.Create(errorCode))
```

## Provide values

You create a value of a structure as an `OpcDataObject`. The server's `GetDataType` method returns the data type it needs as soon as the data type node is published. You access the fields by their name. Optional fields to which you assign no value stay empty. The value of an enumeration is a number: the index for EnumStrings, the value of the entry for EnumValues:

```csharp
var dataType = server.GetDataType(machineDataTypeId);
var data = new OpcDataObject(dataType);
 
data["Speed"].Value = 1200;
data["JobName"].Value = "JOB0815";
data["Temperatures"].Value = new double[] { 20.5, 21.0, 21.3 };
 
machineDataNode.Value = data;
machineDataNode.ApplyChanges(server.SystemContext);
 
// Enumeration values are numbers (index or value of the field), here: Maintenance.
jobKindNode.Value = 1;
jobKindNode.ApplyChanges(server.SystemContext);
```

```vbnet
Dim dataType = server.GetDataType(machineDataTypeId)
Dim data = New OpcDataObject(dataType)

data("Speed").Value = 1200
data("JobName").Value = "JOB0815"
data("Temperatures").Value = New Double() {20.5, 21.0, 21.3}

machineDataNode.Value = data
machineDataNode.ApplyChanges(server.SystemContext)

'Enumeration values are numbers (index or value of the field), here: Maintenance.
jobKindNode.Value = 1
jobKindNode.ApplyChanges(server.SystemContext)
```

A client that resolves the data types of the server at runtime (for example through `OpcClient.UseDynamic`) also receives the values of the structure as `OpcDataObject`. The [Structured data](https://www.traeger.de/en/docs/development/opcua/client/structured-data) page describes this.
