docs.intersystems.com
Home / Using the InterSystems Native API for .NET / Working with Global Arrays

Using the InterSystems Native API for .NET
Working with Global Arrays
Previous section           Next section
InterSystems: The power behind what matters   
Search:  


This chapter covers the following topics:
Creating, Updating, and Deleting Nodes
This section describes the Native API methods used to create, update, and delete nodes. Set(), Increment(), and Kill() are the only methods that can create a global array or alter its contents. The following examples demonstrate how to use each of these methods.
Setting and changing node values
IRIS.Set() takes a value argument and stores the value at the specified address. If no node exists at that address, a new one is created.
The Set() method can assign values of any supported datatype. In the following example, the first call to Set() creates a new node at subnode address ^myGlobal("A") and sets the value of the node to string "first". The second call changes the value of the subnode, replacing it with integer 1.
  native.Set("first", "myGlobal", "A");     // create node ^myGlobal("A") = "first"
  native.Set(1, "myGlobal", "A");   // change value of ^myGlobal("A") to 1.
Set() is a polymorphic accessor that can create and change values of any supported datatype, as demonstrated in this example. To read an existing value, you must use a different Get<Type>() accessor method for each datatype.
Incrementing node values
IRIS.Increment() takes an integer number argument, increments the node value by that amount, and returns the incremented value. It uses a thread-safe atomic operation to change the value of the node, so the node is never locked. If there is no node at the target address, the method creates one and assigns the number argument as the value. In the following example, the first call to Increment() creates new subnode ^myGlobal("B") with value -2. The next two calls each increment by -2, resulting in a final value of -6:
  for (int loop = 0; loop < 3; loop++) {
    native.Increment(-2,"myGlobal", "B");
  }
Note:
—Naming rules—
Global names and subscripts obey the following rules:
Deleting a node or group of nodes
IRIS.Kill() — deletes the specified node and all of its subnodes. The entire global array will be deleted if the root node is deleted or if all nodes with values are deleted.
Global array ^myGlobal initially contains the following nodes:
   ^myGlobal = <valueless node>
     ^myGlobal("A") = 0
       ^myGlobal("A",1) = 0
       ^myGlobal("A",2) = 0
     ^myGlobal("B") = <valueless node>
       ^myGlobal("B",1) = 0
This example will delete the global array by calling Kill() on two of its subnodes. The first call will delete ^myGlobal("A") and both of its subnodes:
  native.Kill("myGlobal", "A"); 
  // also kills child nodes ^myGlobal("A",1) and ^myGlobal("A",2)
The second call deletes the last remaining subnode with a value, killing the entire global array:
  native.Kill("myGlobal","B",1);
Of course, the global array could also have been deleted by calling Kill() on root node ^myGlobal:
  native.Kill("myGlobal");
Finding Nodes in a Global Array
The Native API provides ways to iterate over part or all of a global array. The following topics describe the various iteration methods:
Iterating Over a Set of Child Nodes
Child nodes are sets of nodes immediately under the same parent node. Any child node address can be defined by appending one subscript to the subscript list of the parent. For example, the following global array has four child nodes under parent node ^heros("dogs"):
The ^heros global array
This global array uses the names of several heroic dogs (plus a reckless boy and a pioneering sheep) as subscripts. The values are birth years.
  ^heros                                           // root node,    valueless, 2 child nodes
     ^heros("dogs")                                // level 1 node, valueless, 4 child nodes
        ^heros("dogs","Balto") = 1919              // level 2 node, value=1919
        ^heros("dogs","Hachiko") = 1923            // level 2 node, value=1923
        ^heros("dogs","Lassie") = 1940             // level 2 node, value=1940, 1 child node
           ^heros("dogs","Lassie","Timmy") = 1954  // level 3 node, value=1954
        ^heros("dogs","Whitefang") = 1906          // level 2 node, value=1906
     ^heros("sheep")                               // level 2 node, valueless, 1 child node
        ^heros("sheep","Dolly") = 1996             // level 2 node, value=1996
The following methods are used to create an iterator, define the direction of iteration, and set the starting point of the search:
Read child node values in reverse order
The following code iterates over child nodes of ^heros("dogs") in reverse collation order, starting with subscript V:
// Create a reverse iterator for child nodes of ^heros("dogs")
    IRISIterator iterDogs = native.GetIRISReverseIterator("heros","dogs");
// Start the search with subscript "V" and iterate to lowest collation value
    iterDogs.StartFrom("V");

    Console.Write("Dog birth years: "); 
    foreach (int BirthYear in iterDogs) {
        Console.Write(BirthYear + " "); 
    };
This code prints the following output:
  Dog birth years: 1940 1923 1919
The example does the following:
Two subnodes of ^heros("dogs") are ignored:
See the last section in this chapter (Testing for Child Nodes and Node Values) for a discussion of how to iterate over multiple node levels.
Note:
Collation Order
The order in which nodes are retrieved depends on the collation order of the subscripts. This is not a function of the iterator. When a node is created, it is automatically stored it in the collation order specified by the storage definition. In this example, the child nodes of ^heros("dogs") would be stored in the order shown (Balto, Hachiko, Lassie, Whitefang) regardless of the order in which they were created. For more information, see Collation of Global Nodes in Using Globals.
Iteration in Conditional Loops
The previous section demonstrated an easy way to make a single pass over a set of child nodes, but in some cases you may want more control than a simple foreach loop can provide. This section demonstrates some methods and properties that allow more control over the iterator and provide easier access to data:
Like the previous example, this one uses the ^heros global array and iterates over the child nodes under ^heros("dogs"). However, this example uses the same iterator to make several passes over the child nodes, and exits a loop as soon as certain conditions are met.
Search for values that match items in a list
This example scans the child nodes under ^heros("dogs") until it finds a specific node value or runs out of nodes. Array targetDates specifies the list of targetYear values to be used in the main foreach loop. Within the main loop, the do while loop finds each child node and compares its value to the current targetYear.
  IRISIterator iterDogs = native.GetIRISIterator("^heros","dogs");
  bool seek;
  int[] targetDates = {1906, 1940, 2001};
  foreach (int targetYear in targetDates) {
    do {
      seek = iterDogs.MoveNext();
      if (!seek) {
        Console.WriteLine("Could not find a dog born in " + targetYear);
      }
      else if ((int)iterDogs.Current == targetYear) {
        Console.WriteLine(iterDogs.CurrentSubscript + " was born in " + iterDogs.Current);
        seek = false;
      }
    } while (seek);
    iterDogs.Reset();
  } // end foreach
This code prints the following output:
  Whitefang was born in 1906
  Lassie was born in 1940
  Could not find a dog born in 2001
The example does the following:
Testing for Child Nodes and Node Values
In the previous examples, the scope of the search is restricted to child nodes of ^heros("dogs"). The iterator fails to find two values in global array ^heros because they are under different parents:
To search the entire global array, we need to find all of the nodes that have child nodes, and create an iterator for each one. The IsDefined() method provides the necessary information:
The following example consists of two methods:
Method ProcessNode()
  public bool ProcessNode(IRISIterator iter, string root, params object[] subscripts) {
  // Test for values and child nodes
    int state = native.IsDefined(root,subscripts);
    bool hasValue = (state%10 > 0); // has value if state is 1 or 11

  // Look for lost heros
    string[] lost = {"Timmy","Dolly"};
    if (hasValue) { // ignore valueless nodes 
      string name = (string)iter.CurrentSubscript; 
      int year = (int)iter.Current;
      foreach (string hero in lost) { 
        if (hero == name) {
          Console.WriteLine("Hey, we found " + name + " (born in " + year + ")!!!");
        }
      }
    }

    bool hasChild = (state > 9);    // has child if state is 10 or 11
    return hasChild;
  }
Method FindLostHeros()
This example processes a known structure, and traverses the various levels with simple nested calls. In the less common case where a structure has an arbitrary number of levels, a recursive algorithm could be used.
  public void FindLostHeros() {
    string root = "heros";

  // Iterate over child nodes of root node ^heros
    IRISIterator iterRoot = native.GetIRISIterator(root);
    foreach (object node in iterRoot) {
      object sub1 = iterRoot.CurrentSubscript;
      bool hasChild1 = ProcessNode(iterRoot,sub1);

      // Process current child of ^heros(sub1)
      if (hasChild1) {
        IRISIterator iterOne = native.GetIRISIterator(root,sub1);
        foreach (object node in iterOne) {
          object sub2 = iterOne.CurrentSubscript;
          bool hasChild2 = ProcessNode(iterOne,sub1,sub2);

        // Process current child of ^heros(sub1,sub2)
          if (hasChild2) {
            IRISIterator iterTwo = native.GetIRISIterator(root,sub1,sub2);
            foreach (object node in iterTwo) {
              object sub3 = iterTwo.CurrentSubscript;
              ProcessNode(iterTwo,sub1,sub2,sub3); //no child nodes below level 3
            }
          } //end hasChild2
        }
      } //end hasChild1
    } // end main loop
  } // end FindLostHeros()
Transactions and Locking
The following topics are discussed in this section:
Controlling Transactions
The Native API provides the following methods to control transactions:
The following example starts three levels of nested transaction, setting the value of a different node in each transaction level. All three nodes are printed to prove that they have values. The example then rolls back the second and third levels and commits the first level. All three nodes are printed again to prove that only the first node still has a value.
Controlling Transactions: Using three levels of nested transaction
  String globalName = "myGlobal";
  native.TStart();

// GetTLevel() is 1: create ^myGlobal(1) = "firstValue"
  native.Set("firstValue", globalName, native.GetTLevel());  

  native.TStart();
// GetTLevel() is 2: create ^myGlobal(2) = "secondValue"
  native.Set("secondValue", globalName, native.GetTLevel());

  native.TStart();
// GetTLevel() is 3: create ^myGlobal(3) = "thirdValue"
  native.Set("thirdValue", globalName, native.GetTLevel());

  Console.WriteLine("Node values before rollback and commit:");
  for (int ii=1;ii<4;ii++) { 
    Console.Write(globalName + "(" + ii + ") = ");
    if (native.IsDefined(globalName,ii) > 1) Console.WriteLine(native.getString(globalName,ii));
    else Console.WriteLine("<valueless>");
  }
// prints: Node values before rollback and commit:
//         ^myGlobal(1) = firstValue
//         ^myGlobal(2) = secondValue
//         ^myGlobal(3) = thirdValue

  native.TRollbackOne();
  native.TRollbackOne();  // roll back 2 levels to getTLevel 1
  native.TCommit();  // GetTLevel() after commit will be 0
  Console.WriteLine("Node values after the transaction is committed:");
  for (int ii=1;ii<4;ii++) {
    Console.Write(globalName + "(" + ii + ") = ");
    if (native.IsDefined(globalName,ii) > 1) Console.WriteLine(native.getString(globalName,ii));
    else Console.WriteLine("<valueless>");
  }
// prints: Node values after the transaction is committed:
//         ^myGlobal(1) = firstValue
//         ^myGlobal(2) = <valueless>
//         ^myGlobal(3) = <valueless>
Acquiring and Releasing Locks
The following methods of class IRIS are used to acquire and release locks. Both methods take a lockMode argument to specify whether the lock is shared or exclusive:
   lock (String lockMode, Integer timeout, String globalName, String...subscripts)  final boolean
   unlock (String lockMode, String globalName, String...subscripts)  final void
The following argument values can be used:
Note:
You can use the Management Portal to examine locks. Go to [System Operation] > [Locks] to see a list of the locked items on your system.
Using Locks in a Transaction
This section demonstrates incremental locking within a transaction, using the methods previously described (see Controlling Transactions and Acquiring and Releasing Locks). You can see a list of the locked items on your system by opening the Management Portal and going to [System Operation] > [Locks]. The calls to ReadKey() in the following code will pause execution so that you can look at the list whenever it changes.
There are two ways to release all currently held locks:
The following examples demonstrate the various lock and release methods.
Using incremental locking in transactions
  native.Set("my node", "nodeRef1", "my-node");
  native.Set("shared node", "nodeRef2", "shared-node");

  try {
    native.TStart();
// lock ^nodeRef1("my-node") exclusively
    native.Lock("E",10,"nodeRef1", "my-node");
// lock ^nodeRef2 shared
    native.Lock("ES",10,"nodeRef2", "shared-node");
    Console.WriteLine("Exclusive lock on ^nodeRef1(\"my-node\") and shared lock on ^nodeRef2");
    Console.WriteLine("Press return to release locks individually");
    Console.ReadKey(); // Wait for user to press Return

// release ^nodeRef1("my-node") after transaction
    native.Unlock("E",,"nodeRef1", "my-node");
// release ^nodeRef2 immediately
    native.Unlock("ES",,"nodeRef2", "shared-node");
    Console.WriteLine("Press return to commit transaction");
    Console.ReadKey();
    native.TCommit();
  }
  catch (Exception e) { Console.WriteLine(e.Message); }
  catch (System.IO.IOException e) { Console.WriteLine(e.Message); }
Using non-incremental locking in transactions
// lock ^nodeRef1("my-node") non-incremental
  native.Lock("",10,"nodeRef1", "my-node");
  Console.WriteLine("Exclusive lock on ^nodeRef1(\"my-node\"), return to lock ^nodeRef1 non-incrementally");
  Console.ReadKey();

// lock ^nodeRef2 shared non-incremental
  native.Lock("S",10,"nodeRef2", "shared-node");
  Console.WriteLine("Verify that only ^nodeRef2 is now locked, then press return");
  Console.ReadKey();
Using ReleaseAllLocks() in transactions to release all incremental locks
  // lock ^nodeRef1("my-node") shared incremental
  native.Lock("SE",10,"nodeRef1", "my-node");;

// lock ^nodeRef2 exclusive incremental
  native.Lock("E",10,"nodeRef2", "shared-node");
  Console.WriteLine("Two locks are held (one with lock count 2), return to release both locks");
  Console.ReadKey();

  native.ReleaseAllLocks();
  Console.WriteLine("Verify both locks have been released");
  Console.ReadKey();


Previous section           Next section
View this book as PDF   |  Download all PDFs
Copyright © 1997-2019 InterSystems Corporation, Cambridge, MA
Content Date/Time: 2019-04-10 14:45:55