This article contains information and guidelines to migrate code written for the DLL4 interface from previous versions (< v5.0.0) to the latest version using multi threaded interface processes.
This migration is optional, backwards compatibility can be maintained by using Isolation : Dedicated in the profile. Using this option will increase the memory usage by the process (due to the assemblies being loaded multiple times), consider increasing the maximum memory setting to prevent frequent recycling of the DLL process. This option may be phased out in future versions.
Overview
Below is an overview of the changes to the DLL4 interface and the actions to take when migrating code.
DLL4 method code is executed on the same process.
The static Interface properties are no longer available.
Code should implement IRequestProcessor instead of defining a static function.
Interface scripts must now define the name of the class to instantiate and call instead of a function name.
Example Migration
The sample below shows the code for 2 DLL methods: CreateEquipment and UpdateEquipment. A different class EquipmentServices is used for reuseable functions. The tabs show the steps taken to migrate this code.
public static class EntryMethods
{
private static DateTime s_startedOn;
public static void CreateEquipment()
{
Interface.Log.Write("Creating equipment...", Ometa.Logging.Log.Category.Information);
s_startedOn = DateTime.Now;
EquipmentServices.CurrentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
if (EquipmentServices.CurrentEquipmentCategory == "1")
return;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.CreateEquipment(bcClient);
Interface.Log.PropertiesHelper.Add("Function Started", s_startedOn);
Interface.Log.Write("Equipment created.", Ometa.Logging.Log.Category.Information);
if (string.IsNullOrWhiteSpace(equipmentNumber))
throw new Exception("Equipment number was not returned from the equipment creation.");
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
}
public static void UpdateEquipment()
{
Interface.Log.Write("Updating equipment...", Ometa.Logging.Log.Category.Information);
s_startedOn = DateTime.Now;
EquipmentServices.CurrentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
var equipment = Interface.Method.GetFieldValueForExecution<string>("Equipment");
if (EquipmentServices.CurrentEquipmentCategory == "1")
return;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.UpdateEquipment(bcClient, equipment);
Interface.Log.PropertiesHelper.Add("Function Started", s_startedOn);
Interface.Log.Write("Equipment updated.", Ometa.Logging.Log.Category.Information);
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
}
}
public static class EquipmentServices
{
internal static string CurrentEquipmentCategory;
internal static string CreateEquipment(BCClient bcClient)
{
var equipmentNumber = string.Empty;
var createEquipmentMethod = bcClient.GetObject(Interface.Method.ParentObject.Name).GetMethod("Create Equipment");
if (createEquipmentMethod == null)
throw new Exception("The method 'Create Equipment' could not be found.");
createEquipmentMethod.SetFieldValueForExecution("EQTYP", CurrentEquipmentCategory);
foreach (var aDataExtension in createEquipmentMethod.DataExtensions)
createEquipmentMethod.SetDataExtensionProfileForExecution(aDataExtension.Id, "SAP PRD");
using (var resultSet = createEquipmentMethod.Execute("SAP Equipment", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
internal static string UpdateEquipment(BCClient bcClient, string equipment)
{
var equipmentNumber = string.Empty;
var updateEquipment = bcClient.GetObject(Interface.Method.ParentObject.Name).GetMethod("Update Equipment");
if (updateEquipment == null)
throw new Exception("The method 'Update Equipment' could not be found.");
updateEquipment.SetFieldValueForExecution("Equipment", equipment);
updateEquipment.SetFieldValueForExecution("EQTYP", CurrentEquipmentCategory);
using (var resultSet = updateEquipment.Execute("OData v2 SAP", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
}
The first step is to implement Ometa.Interfaces.IRequestProcessor for the entry methods, in this case CreateEquipment and UpdateEquipment. This interface is located in the Ometa.CommLib assembly. To implement this, each entry method will become a class and the logic is moved to ProcessRequestAsync.
public class CreateEquipment : IRequestProcessor
{
private static DateTime s_startedOn;
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
Interface.Log.Write("Creating equipment...", Ometa.Logging.Log.Category.Information);
s_startedOn = DateTime.Now;
EquipmentServices.CurrentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
if (EquipmentServices.CurrentEquipmentCategory == "1")
return Task.CompletedTask;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.CreateEquipment(bcClient);
Interface.Log.PropertiesHelper.Add("Function Started", s_startedOn);
Interface.Log.Write("Equipment created.", Ometa.Logging.Log.Category.Information);
if (string.IsNullOrWhiteSpace(equipmentNumber))
throw new Exception("Equipment number was not returned from the equipment creation.");
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
return Task.CompletedTask;
}
}
public class UpdateEquipment : IRequestProcessor
{
private static DateTime s_startedOn;
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
Interface.Log.Write("Updating equipment...", Ometa.Logging.Log.Category.Information);
s_startedOn = DateTime.Now;
var equipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
var technicalObjectType = Interface.Method.GetFieldValueForExecution<string>("ObjectSoort");
EquipmentServices.CurrentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Equipment");
if (EquipmentServices.CurrentEquipmentCategory == "1")
return Task.CompletedTask;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.UpdateEquipment(bcClient, equipmentId, equipmentCategory, technicalObjectType);
Interface.Log.PropertiesHelper.Add("Function Started", s_startedOn);
Interface.Log.Write("Equipment updated.", Ometa.Logging.Log.Category.Information);
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
return Task.CompletedTask;
}
}
public static class EquipmentServices
{
internal static string CurrentEquipmentCategory;
internal static string CreateEquipment(BCClient bcClient)
{
var equipmentNumber = string.Empty;
var createEquipmentMethod = bcClient.GetObject(Interface.Method.ParentObject.Name).GetMethod("Create Equipment");
if (createEquipmentMethod == null)
throw new Exception("The method 'Create Equipment' could not be found.");
createEquipmentMethod.SetFieldValueForExecution("EQTYP", CurrentEquipmentCategory);
foreach (var aDataExtension in createEquipmentMethod.DataExtensionSettings)
createEquipmentMethod.SetDataExtensionProfileForExecution(aDataExtensionSetting.Identifier, "SAP PRD");
using (var resultSet = createEquipmentMethod.Execute("SAP Equipment", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
internal static string UpdateEquipment(BCClient bcClient, string equipment)
{
var equipmentNumber = string.Empty;
var updateEquipment = bcClient.GetObject(Interface.Method.ParentObject.Name).GetMethod("Update Equipment");
if (updateEquipment == null)
throw new Exception("The method 'Update Equipment' could not be found.");
updateEquipment.SetFieldValueForExecution("Equipment", equipment);
updateEquipment.SetFieldValueForExecution("EQTYP", CurrentEquipmentCategory);
using (var resultSet = updateEquipment.Execute("OData v2 SAP", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
}
A few changes were applied:
The entry methods CreateEquipment and UpdateEquipment are changed to classes that implement IRequestProcessor. The contents of the old functions were moved to ProcessRequestAsync functions.
The signature of the functions was changed from public void to public Task. This is done to support using async code, since this sample does not use any async functions, the line return Task.CompletedTask; is added at the end of the function.
The function string CreateEquipment(BCClient bcClient) in EquipmentServices was modified to use the updated properties to set data extension profiles.
The Interface property can be used to interact with the framework. It is set before ProcessRequestAsync is called.
If you changed the name of the new class in comparison to the old method name (e.g.: CreateEquipment -> EquipmentCreation), the interface script in the method configuration must then be updated as well.
The next step is to validate the usage of static fields and static properties in the code. The following issues are still present:
Because multiple methods can be called in the framework at the same time, there is a risk of the values of static properties geting mixed. In the original code of our sample this is the case for s_startedOn, and the CurrentEquipmentCategory.
Another issue is that the EquipmentServices class is a different class from the entry classes, and it still refers to the deprecated Interface.Method static properties. This will give a runtime error.
To resolve this, the Interface property from the IRequestProcessor is going to be passed through to the functions in the EquipmentServices. The static property is removed in favour of another parameter.
public class CreateEquipment : IRequestProcessor
{
private DateTime _startedOn;
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
Interface.Log.Write("Creating equipment...", Ometa.Logging.Log.Category.Information);
_startedOn = DateTime.Now;
var currentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
if (currentEquipmentCategory == "1")
return Task.CompletedTask;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.CreateEquipment(Interface, bcClient, currentEquipmentCategory);
Interface.Log.PropertiesHelper.Add("Function Started", _startedOn);
Interface.Log.Write("Equipment created.", Ometa.Logging.Log.Category.Information);
if (string.IsNullOrWhiteSpace(equipmentNumber))
throw new Exception("Equipment number was not returned from the equipment creation.");
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
return Task.CompletedTask;
}
}
public class UpdateEquipment : IRequestProcessor
{
private DateTime _startedOn;
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
Interface.Log.Write("Updating equipment...", Ometa.Logging.Log.Category.Information);
_startedOn = DateTime.Now;
var currentEquipmentCategory = Interface.Method.GetFieldValueForExecution<string>("Type");
var equipment = Interface.Method.GetFieldValueForExecution<string>("Equipment");
if (currentEquipmentCategory == "1")
return Task.CompletedTask;
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.UpdateEquipment(Interface, bcClient, equipment, currentEquipmentCategory);
Interface.Log.PropertiesHelper.Add("Function Started", _startedOn);
Interface.Log.Write("Equipment updated.", Ometa.Logging.Log.Category.Information);
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
return Task.CompletedTask;
}
}
public static class EquipmentServices
{
internal static string CreateEquipment(IInterfaceRequest currentInterface, BCClient bcClient, string currentEquipmentCategory)
{
var equipmentNumber = string.Empty;
var createEquipmentMethod = bcClient.GetObject(currentInterface.Method.ParentObject.Name).GetMethod("Create Equipment");
if (createEquipmentMethod == null)
throw new Exception("The method 'Create Equipment' could not be found.");
createEquipmentMethod.SetFieldValueForExecution("EQTYP", currentEquipmentCategory);
using (var resultSet = createEquipmentMethod.Execute("SAP Equipment", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
internal static string UpdateEquipment(IInterfaceRequest currentInterface, BCClient bcClient, string equipment, string currentEquipmentCategory)
{
var equipmentNumber = string.Empty;
var updateEquipment = bcClient.GetObject(currentInterface.Method.ParentObject.Name).GetMethod("Update Equipment");
if (updateEquipment == null)
throw new Exception("The method 'Update Equipment' could not be found.");
updateEquipment.SetFieldValueForExecution("Equipment", equipment);
updateEquipment.SetFieldValueForExecution("EQTYP", currentEquipmentCategory);
using (var resultSet = updateEquipment.Execute("OData v2 SAP", 0))
{
if (!string.IsNullOrWhiteSpace(resultSet.Error))
throw new Exception(resultSet.Error);
if (resultSet.ReadNextRecord())
equipmentNumber = resultSet.GetFieldValue<string>("Equipment");
}
return equipmentNumber;
}
}
The static field and property were removed in favour of an instance field and a local variable.
There are still use cases for statics like caching or managing connections. This is still possible but they must be protected from concurrent calls using locks or thread safe types like ConcurrentDictionary. The correlation id of the Interface.Log can be used to track running worker threads.
Interface Scripts
The interface scripts must now define the name of the class to instantiate and call instead of a function name.
First line is the dll file name, including extension.
Second line is the static entry method name, this could be left empty in which case the static ProcessRequest method was used if available.
namespace Equipment_Sample
{
public static class EntryMethods
{
public static void CreateEquipment()
{
Interface.Log.Write("Creating equipment...", Ometa.Logging.Log.Category.Information);
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.CreateEquipment(bcClient);
Interface.Log.Write("Equipment created.", Ometa.Logging.Log.Category.Information);
if (string.IsNullOrWhiteSpace(equipmentNumber))
throw new Exception("Equipment number was not returned from the equipment creation.");
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
}
}
}
Interface script:
Equipment Sample.dll
CreateEquipment
First line is the dll file name, including extension.
Second line is the class name.
namespace Equipment_Sample
{
public class CreateEquipment : IRequestProcessor
{
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
Interface.Log.Write("Creating equipment...", Ometa.Logging.Log.Category.Information);
using (var bcClient = Interface.ConnectedClient.CreateNewClient())
{
var equipmentNumber = EquipmentServices.CreateEquipment(Interface, bcClient, currentEquipmentCategory);
Interface.Log.Write("Equipment created.", Ometa.Logging.Log.Category.Information);
if (string.IsNullOrWhiteSpace(equipmentNumber))
throw new Exception("Equipment number was not returned from the equipment creation.");
Interface.ResultWriter.WriteBeginRecord();
Interface.ResultWriter.WriteFieldValue("Equipment", equipmentNumber);
Interface.ResultWriter.WriteEndRecord();
}
return Task.CompletedTask;
}
}
}
BuildingBlocks
For code that uses the Ometa BuildingBlocks through DLL4 method code; the namespace of the Ometa BuildingBlocks has moved from Ometa.Utilities.Client to Ometa.Framework.BuildingBlocks to enable multi threading on the interface process.
Note
Backwards compatibility can be maintained by using Isolation : Dedicated in the profile. A runtime error will be thrown otherwise.
The only major change is that the BuildingBlocks no longer use static classes and functions. It is recommended to migrate the custom code following the previous chapter and use the Interface instance member.
public static class Sample
{
public static void ReceiveMessage()
{
Ometa.Utilities.Client.SharePoint.Lists.GetList(web, "My List");
}
}
public class Sample : IRequestProcessor
{
public IInterfaceRequest Interface { get; set; }
public Task ProcessRequestAsync()
{
var listBuildingBlocks = new Ometa.Framework.BuildingBlocks.List(Interface.Log);
listBuildingBlocks.GetList(web, "My List");
return Task.CompletedTask;
}
}
Coding with Data Extensions
Due to the fact that data extension have been moved to the core layers, existing code which targets data extensions will possibly break.
var myObject = bcClient.GetObject("MyObject");
var myMethod = myObject.GetMethod("MyMethod");
// Usage of any of the following lines will break your code (compilation error):
var dataExtensions = myMethod.DataExtensions;
var inputDataExtensions = myMethod.InputDataExtensions;
var outputDataExtensions = myMethod.OutputDataExtensions;
var allDataExtensions = myMethod.AllDataExtensions;
var newDataExtension = myMethod.AddDataExtension(true);
var myDataExtension = myMethod.GetDataExtension(true, "MyExtensionObject", "MyExtensionMethod");
myMethod.RemoveDataExtension(myDataExtension);
DataExtension myDataExtensionInAVariable = null;
FieldRelation myFieldRelationInAVariable = null;
In most cases, the only code that should be fixed is the one which is responsible for setting data extension profiles on a method which is going to be executed.
The following example shows how to change your existing code.
var myObject = bcClient.GetObject("MyObject");
var myMethod = myObject.GetMethod("MyMethod");
// Your original code could look like this (and will now break compilation):
foreach (var aDataExtension in myMethod.DataExtensions)
{
myMethod.SetDataExtensionProfileForExecution(aDataExtension.Id, "myProfile");
}
// The above code can be replaced by:
foreach (var aDataExtensionSetting in myMethod.DataExtensionSettings)
{
myMethod.SetDataExtensionProfileForExecution(aDataExtensionSetting.Identifier, "myProfile");
}
The new DataExtensionSetting class is contained in our new Ometa.Framework.Client API (which will eventually replace the whole Ometa.CommLib and Ometa.Dcs libraries).
The new property Method.DataExtensionSettings uses this class so existing code can be migrated with as little effort as possible.
If you, however, have code in place which also manages data extensions through the old Ometa.CommLib data extension class, let us know so we can help migrate your codebase.