Monday, September 16, 2013

Using System.AppDomain to share data between Automation Objects - Part 2

In Part 1 I introduced you to the System.AppDomain .NET class a way to share complex types between objects in Application Server (as long as the objects are hosted in the same AppEngine). As an example, I shared a database connection to the Historian Server database (Runtime) through the AppEngine object.

Now, let's take a look at the sample script needed in the other objects to access the shared database connection.

With the AppEngine in charge of opening and closing the database connection, the application object only needs to retrieve the connection and then use it. Assuming that the connection will be used multiple times during the execution of the object, it makes sense to retrieve the connection during Startup script and use it on Execute based on whatever condition you need.

For the purpose of this example, I will use a simple trigger UDA to run the Execute portion of the script and capture the result of a query on another UDA. Let's create the following UDAs:

NameData TypeCategory
TriggerBooleanUser writeable
ResultStringCalculated

The System.AppDomain part of this script is only one line of code. The rest of the script is just good practices and running a query to test the script.

Declarations section:

dim conn as System.Data.SqlClient.SqlConnection;
Startup script:
'Get database connection from AppDomain.
conn = System.AppDomain.CurrentDomain.GetData("RuntimeDbConnection");
Execute script:
'Verify a connection is available.
if (conn <> null) then
    'Block database connection object.
    System.Threading.Monitor.Enter(conn);

    dim cmd as System.Data.SqlClient.SqlCommand;
    cmd = conn.CreateCommand();
    cmd.CommandText = "select Count(Tagname) from Tag";

    dim reader as System.Data.SqlClient.SqlDataReader;
    reader = cmd.ExecuteReader();
    reader.Read();
    Me.Result = reader[0];

    'Unblock database connection object.
    System.Threading.Monitor.Exit(conn);
endif;

'Reset flag.
Me.Trigger = false;
Let's take a look at the workflow for this script:
  1. On Startup, retrieve the database connection from the current domain.
  2. On Execute, verify the connection exists.
  3. Block the database connection.
  4. Run a query and return the value.
  5. Unblock the database connection.
Let's take a closer look at this script.

dim conn as System.Data.SqlClient.SqlConnection;
The database connection is a .NET instance. The conn variable created in the AppEngine and the one created here are simply pointers to that instance. We need this pointer to be able to address the connection. Since we are going to keep this pointer around while the object is executing, it must be declared in the Declarations section of the script.

conn = System.AppDomain.CurrentDomain.GetData("RuntimeDbConnection");
When the object starts, simply retrieve the connection from the current domain by using the same name indicated in the AppEngine script, RuntimeDbConnection. It that simple.

if (conn <> null) then
    ...
endif;
If the AppEngine failed to open the database connection, it never got place in the domain, so it is wise to at least check if conn is pointing to something before continuing. It will be also useful to check if the connection is still open, but that has nothing to do with System.AppDomain so I'll skip that part.

System.Threading.Monitor.Enter(conn);
Since the connection to the database is being shared within that AppEngine, any object could access the connection at any time. As a good practice it's best to block access to the connection .NET instance before using it.

dim cmd as System.Data.SqlClient.SqlCommand;
cmd = conn.CreateCommand();
cmd.CommandText = "select Count(Tagname) from Tag";
This has nothing to do with System.AppDomain, but with the database connection so we can test the script. We create a SQL command for the query. For this example I'm simply going to retrieve the number of tags defined in the Historian database.

dim reader as System.Data.SqlClient.SqlDataReader;
reader = cmd.ExecuteReader();
reader.Read();
Me.Result = reader[0];
More database stuff to test the script. We execute the query, read the result and expose the value through the Result UDA.

System.Threading.Monitor.Exit(conn);
Keeping it up with the good practices. If we blocked the access to the connection .NET instance, we better unblock it so other objects in the AppEngine can use it.

Me.Trigger = false;
For good measure, let's reset the flag that triggered the script so it can be easily re-triggered again.

The technique used here can be applied to other .NET classes, such as access to an XML file, a web service, or a .NET collection. If you need to share a Boolean value or any other ArchestrA data type, simply use a UDA; remember that you can create hidden UDAs also, in case you don’t want to expose it thought the Galaxy Browser.

No comments:

Post a Comment