.NET Native SDK は、InterSystems IRIS のインスタンスからグローバル配列を操作するメカニズムを提供します。この章では、以下の項目について説明します。
Note:
.NET 接続の作成
この章の例では、iris という名前の IRIS オブジェクトが既に存在し、サーバに接続されていると想定します。以下のコードは、標準の .NET 接続を確立し、IRIS のインスタンスを作成します。
//Open a connection to the server and create an IRIS object
IRISConnection conn = new IRISConnection();
conn.ConnectionString = "Server = localhost; "
+ "Port = 1972; " + "Namespace = USER; "
+ "Password = SYS; " + "User ID = _SYSTEM;";
conn.Open();
IRIS iris = IRIS.CreateIRIS(conn);
IRIS のインスタンスの作成方法の詳細は、CreateIRIS() のクイック・リファレンスのエントリを参照してください。.NET 接続の作成方法の一般的な情報は、"InterSystems ソフトウェアでの .NET の使用法" の "接続の作成" を参照してください。.
グローバル配列の概要
グローバル配列は、すべてのスパース配列のように、ツリー構造です (シーケンシャル・リストではありません)。グローバル配列の背後にある基本概念は、ファイル構造に例えて示すことができます。ツリーの各ディレクトリは、ルート・ディレクトリ識別子とそれに続く一連のサブディレクトリ識別子で構成されるパスによって一意に識別され、ディレクトリにはデータが含まれることも、含まれないこともあります。
グローバル配列の仕組みも同じです。ツリーの各ノードは、グローバル名識別子と一連の添え字識別子で構成されるノード・アドレスによって一意に識別され、ノードには値が含まれることも、含まれないこともあります。例えば、以下は 6 つのノードで構成されるグローバル配列で、そのうち 2 つのノードには値が含まれます。
root -->|--> foo --> SubFoo="A"
|--> bar --> lowbar --> UnderBar=123
値はその他のノード・アドレス (root または root->bar など) に格納できますが、それらのノード・アドレスが値なしの場合、リソースは無駄になりません。InterSystems ObjectScript グローバルの表記では、値を持つ 2 つのノードは次のようになります。
root("foo","SubFoo")
root("bar","lowbar","UnderBar")
グローバル名 (root) の後に、括弧で囲まれたコンマ区切り添え字リストが続きます。この両者で、ノードのパス全体を指定します。
このグローバル配列は、Native SDK Set() メソッドへの 2 つの呼び出しで作成されます。
irisObject.Set("A", "root", "foo", "SubFoo");
irisObject.Set(123, "root", "bar", "lowbar", "UnderBar");
グローバル配列 root は、最初の呼び出しが値 "A" をノード root("foo","SubFoo") に割り当てるときに作成されます。ノードは任意の順序で、任意の添え字セットを使用して作成できます。これら 2 つの呼び出しの順序を逆にした場合でも、同じグローバル配列が作成されます。値なしノードは自動的に作成され、不要になると自動的に削除されます。詳細は、この章で後述する “ノードの作成、アクセス、および削除” を参照してください。
この配列を作成する Native SDK コードを、以下に例示します。IRISConnection オブジェクトは、サーバへの接続を確立します。この接続は、iris という名前のクラス IRIS のインスタンスによって使用されます。Native SDK メソッドを使用してグローバル配列を作成し、生成された永続値をデータベースから読み取った後、グローバル配列を削除します。
NativeDemo プログラム
Native SDK for .NET は、InterSystems.Data.IrisClient.dll ライブラリに含まれます。詳細は、"InterSystems ソフトウェアでの .NET の使用法" の "Introduction" を参照してください。
using System;
using InterSystems.Data.IRISClient;
using InterSystems.Data.IRISClient.ADO;
namespace NativeSpace {
class NativeDemo {
static void Main(string[] args) {
try {
//Open a connection to the server and create an IRIS object
IRISConnection conn = new IRISConnection();
conn.ConnectionString = "Server = localhost; "
+ "Port = 1972; " + "Namespace = USER; "
+ "Password = SYS; " + "User ID = _SYSTEM;";
conn.Open();
IRIS iris = IRIS.CreateIRIS(conn);
//Create a global array in the USER namespace on the server
iris.Set("A", "root", "foo", "SubFoo");
iris.Set(123, "root", "bar", "lowbar", "UnderBar");
// Read the values from the database and print them
string subfoo = iris.GetString("root", "foo", "SubFoo");
string underbar = iris.GetString("root", "bar", "lowbar", "UnderBar");
Console.WriteLine("Created two values: \n"
+ " root(\"foo\",\"SubFoo\")=" + subfoo + "\n"
+ " root(\"bar\",\"lowbar\",\"UnderBar\")=" + underbar);
//Delete the global array and terminate
iris.Kill("root"); // delete global array root
iris.Close();
conn.Close();
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}// end Main()
} // end class NativeDemo
}
NativeDemo は、以下の行を出力します。
Created two values:
root("foo","SubFoo")=A
root("bar","lowbar","UnderBar")=123
この例では、conn という名前の IRISConnection オブジェクトが、USER ネームスペースに関連付けられているデータベースへの接続を提供します。Native SDK メソッドは以下のアクションを実行します。
-
IRIS.CreateIRIS() は、conn を介してデータベースにアクセスする、iris という名前の IRIS の新しいインスタンスを作成します。
-
IRIS.Set() は、新しい永続ノードをデータベースに作成します。
-
IRIS.GetString() は、データベースに対してクエリを実行して、指定されたノードの値を返します。
-
IRIS.Kill() は、指定されたノードとそのサブノードすべてをデータベースから削除します。
次の章では、これらのすべてのメソッドについて詳細に説明し、例を示します。
Native SDK 用語の用語集
ここに示す概念の概要については、前のセクションを参照してください。この用語集の例は、以下に示すグローバル配列構造を指しています。Legs グローバル配列には、10 個のノードと 3 つのノード・レベルがあります。10 個のノードのうち 7 つには、値が含まれます。
Legs // root node, valueless, 3 child nodes
fish = 0 // level 1 node, value=0
mammal // level 1 node, valueless
human = 2 // level 2 node, value=2
dog = 4 // level 2 node, value=4
bug // level 1 node, valueless, 3 child nodes
insect = 6 // level 2 node, value=6
spider = 8 // level 2 node, value=8
millipede = Diplopoda // level 2 node, value="Diplopoda", 1 child node
centipede = 100 // level 3 node, value=100
子ノード
指定された親ノードの直下にあるノードです。子ノードのアドレスは、親添え字リストの末尾に 1 つの添え字を追加して指定します。例えば、親ノード Legs("mammal") には子ノード Legs("mammal","human") および Legs("mammal","dog") があります。
グローバル名
ルート・ノードの識別子は、グローバル配列全体の名前でもあります。例えば、ルート・ノード識別子 Legs は、グローバル配列 Legs のグローバル名です。
ノード
グローバル配列の要素で、グローバル名と任意の数の添え字識別子で構成されるネームスペースによって一意に識別されます。ノードは、データを含むか、子ノードを持つか、またはこれらの両方を持つ必要があります。
ノード・レベル
ノード・アドレス内の添え字の数。'レベル 2 ノード' は、'2 つの添え字を持つノード' のもう 1 つの表現方法です。例えば、Legs("mammal","dog") は、レベル 2 ノードです。ルート・ノード Legs の 2 レベル下で、Legs("mammal") の 1 レベル下です。
ノード・アドレス
グローバル名とすべての添え字を含む、ノードの完全なネームスペースです。例えば、ノード・アドレス Legs("fish") は、ルート・ノード識別子 Legs と 1 つの添え字 "fish" を含むリストで構成されます。コンテキストに応じて、Legs (添え字リストなし) はルート・ノード・アドレスまたはグローバル配列全体を参照することができます。
ルート・ノード
グローバル配列ツリーの基点にある添え字なしノードです。ルート・ノードの識別子は、その添え字なしのグローバル名です。
サブノード
特定のノードのすべての下位ノードは、そのノードのサブノードと呼ばれます。例えば、ノード Legs("bug") には 2 つのレベルの 4 つの異なるサブノードがあります。9 つの添え字付きノードはすべて、ルート・ノード Legs のサブノードです。
添え字/添え字リスト
ルート・ノードの下にあるノードはすべて、グローバル名および 1 つまたは複数の添え字識別子のリストを指定して処理されます(グローバル名に添え字リストを加えたものが、ノード・アドレスです)。
ターゲット・アドレス
多くの Native SDK メソッドは有効なノード・アドレスを指定する必要がありますが、ノード・アドレスは必ずしも既存のノードを指す必要はありません。例えば、Set() メソッドは value 引数とターゲット・アドレスを取り、そのアドレスに値を格納します。ターゲット・アドレスにノードが存在しない場合は、新しいノードが作成されます。
値
ノードには、サポートされている任意の型の値を含めることができます。子ノードを持たないノードには値を含める必要があり、子ノードを持つノードは値なしにすることができます。
値なしノード
ノードは、データを含むか、子ノードを持つか、またはこれらの両方を持つ必要があります。子ノードを持っているがデータを含まないノードは、値なしノードと呼ばれます。値なしノードは、下位レベルのノードへのポインタとしてのみ存在します。
グローバル命名規則
グローバル名と添え字は、次の規則に従います。
-
ノード・アドレスの長さ (グローバル名とすべての添え字の長さの合計) は最大 511 文字です(入力した一部の文字は、この制限のために、複数のエンコードされた文字としてカウントされることがあります。詳細は、"グローバルの使用法" の “グローバル参照の最大長” を参照してください)。
-
グローバル名には文字、数字、およびピリオド ('.') を使用でき、最大 31 文字の有効文字を使用できます。文字で始まる必要があり、ピリオドで終了することはできません。
-
添え字は文字列または数値にすることができます。文字列の添え字は大文字と小文字が区別され、すべての文字 (制御文字と非表示文字を含む) を使用できます。長さの制限は、ノード・アドレス全体の最大長が 511 文字という制限のみです。
ノードの作成、アクセス、および削除
Native SDK には、データベース内で変更を実行することのできるメソッドが 3 つ用意されています。Set() および Increment() はノード値を作成または変更することができ、Kill() は 1 つのノードまたは一連のノードを削除することができます。ノード値はタイプ固有のゲッター・メソッド (GetInteger() や GetString()) などで取得します。
ノードの作成とノード値の設定
Set() メソッドと Increment() メソッドを使用して、指定した値を持つ永続ノードを作成したり、既存のノードの値を変更したりできます。
IRIS.Set() は、任意のサポートされているデータ型の value 引数を取り、指定されたアドレスにその値を格納します。ターゲット・アドレスにノードが存在しない場合は、新しいノードが作成されます。
ノード値の設定および変更
以下の例では、Set() の最初の呼び出しによって、新しいノードがサブノード・アドレス myGlobal("A") に作成され、このノードの値が文字列 "first" に設定されます。2 回目の呼び出しによって、サブノードの値が変更され、整数 1 に置き換えられます。
iris.Set("first", "myGlobal", "A"); // create node myGlobal("A") = "first"
iris.Set(1, "myGlobal", "A"); // change value of myGlobal("A") to 1.
Set() は、サポートされている任意のデータ型の値を作成および変更できます。既存の値を読み取るには、次のセクションで説明するように、データ型ごとに異なるゲッター・メソッドを使用する必要があります。
IRIS.Increment() は number 引数を取り、その数だけノードの値をインクリメントし、そのインクリメントした値を返します。
ターゲット・アドレスにノードがない場合、このメソッドはノードを作成し、値として number 引数を割り当てます。このメソッドはスレッドセーフなアトミック処理を使用して、ノードの値を変更します。したがって、ノードは決してロックされません。
ノード値のインクリメント
以下の例では、Increment() の最初の呼び出しによって、新しいサブノード myGlobal("B") が値 -2 で作成されます。次の 2 回の呼び出しによって、それぞれ -2 だけインクリメントされ、最終値は -6 になります。
for (int loop = 0; loop < 3; loop++) {
iris.Increment(-2,"myGlobal", "B");
}
Note:
グローバル命名規則
Set() または Increment() の 2 番目の引数は、グローバル配列名です。グローバル配列名には、文字、数字、およびピリオドを使用できます。名前は文字で始まる必要があり、ピリオドで終了することはできません。グローバル名の後の引数は添え字で、これには数値または文字列を指定できます (大文字と小文字が区別され、英数字に限定されません)。詳細は、“グローバル命名規則” を参照してください。
ノード値の取得
Set() メソッドはサポートされているすべてのデータ型で使用できますが、各データ型には別個のゲッターが必要です。ノード値は、以下のいずれかのデータ型になります : int?、short?、string、long?、double?、float?、byte[]、bool?、DateTime?、 IRISList?、および System.IO.MemoryStream を実装するオブジェクトのインスタンス (byte[] として格納および取得)。NULL 値は "" に変換されます。
以下のメソッドを使用して、これらのデータ型からノード値を取得します。
データ型の詳細は、この章で後述する “クラス IRIS のサポートされているデータ型” を参照してください。
ノードの削除
IRIS.Kill() は、指定したノードとそのすべてのサブノードを削除します。ルート・ノードを削除した場合、または値を持つすべてのノードを削除した場合、グローバル配列全体が削除されます。
以下の例では、グローバル配列 myGlobal には最初、以下のノードが含まれています。
myGlobal = <valueless node>
myGlobal("A") = 0
myGlobal("A",1) = 0
myGlobal("A",2) = 0
myGlobal("B") = <valueless node>
myGlobal("B",1) = 0
この例では、myGlobal("A") と myGlobal("B",1) の 2 つのサブノードで Kill() を呼び出して、グローバル配列全体を削除します。
ノードまたはノード・グループの削除
最初の呼び出しによって、ノード myGlobal("A") とその両方のサブノードが削除されます。
iris.Kill("myGlobal", "A");
// also kills child nodes myGlobal("A",1) and myGlobal("A",2)
2 番目の呼び出しによって、値を持つ最後の残りのサブノード myGlobal("B",1) が削除されます。
iris.Kill("myGlobal","B",1);
残りのどのノードにも値がない場合は、グローバル配列全体が削除されます。
グローバル配列のノードの検索
Native SDK は、1 つのグローバル配列の一部または全部に対して反復処理する方法を提供します。以下のトピックでは、さまざまな反復メソッドについて説明します。
一連の子ノードにわたる反復
子ノードは、同じ親ノードの直下にある一連のノードです。親の添え字リストに 1 つの添え字を追加することで、子ノード・アドレスを定義できます。例えば、以下のグローバル配列には、親ノード heroes("dogs") の下に 4 つの子ノードがあります。
heroes グローバル配列
このグローバル配列では、いくつかのヒーロー犬 (および無謀な少年と先導する羊) の名前を添え字として使用します。値は生まれた年です。
heroes // root node, valueless, 2 child nodes
heroes("dogs") // level 1 node, valueless, 4 child nodes
heroes("dogs","Balto") = 1919 // level 2 node, value=1919
heroes("dogs","Hachiko") = 1923 // level 2 node, value=1923
heroes("dogs","Lassie") = 1940 // level 2 node, value=1940, 1 child node
heroes("dogs","Lassie","Timmy") = 1954 // level 3 node, value=1954
heroes("dogs","Whitefang") = 1906 // level 2 node, value=1906
heroes("sheep") // level 2 node, valueless, 1 child node
heroes("sheep","Dolly") = 1996 // level 2 node, value=1996
以下のメソッドを使用して反復子を作成し、反復処理の方向を定義し、検索の開始位置を設定します。
逆順序での子ノード値の読み取り
以下のコードは、heroes("dogs") の子ノードを逆方向の照合順序で反復処理します (添え字 V から開始します)。
// Iterate in reverse, seeking nodes lower than heroes('dogs','V') in collation order
IRISIterator iterDogs = iris.GetIRISReverseIterator("heroes","dogs");
iterDogs.StartFrom("V");
String output = "\nDog birth years: ";
foreach (int BirthYear in iterDogs) {
output += BirthYear + " ";
};
Console.WriteLine(output);
このコードによって、以下のような出力が生成されます。
Dog birth years: 1940 1923 1919
この例は、以下を実行します。
-
GetIRISReverseIterator() は、heroes("dogs") の子ノードを逆方向の照合順序で検索する、反復子 iterDogs を返します。
-
StartFrom() は、添え字 V を指定します。すなわち、検索範囲には、照合順序が V よりも下位の添え字を持つ heroes("dogs") のすべての子コードが含まれます。反復子は、まず添え字 Lassie を検索し、続いて Hachiko および Balto を検索します。
heroes("dogs") の以下の 2 つのサブノードは無視されます。
-
子ノード heroes("dogs","Whitefang") は検索範囲外にあるため、見つかりません (Whitefang は照合順序が V より上位です)。
-
レベル 3 ノード heroes("dogs","Lassie","Timmy") は、dogs ではなく Lassie の子であるため、見つかりません。
複数のノード・レベルにわたって反復処理する方法については、この章の最後のセクション (“子ノードおよびノード値のテスト”) を参照してください。
Note:
照合順序
ノードが取得される順序は、添え字の照合順序によって決まります。これは、反復子の機能ではありません。ノードが作成されると、ストレージ定義で指定された照合順に、自動的にノードが格納されます。この例では、heroes("dogs") の子ノードは、作成された順序に関係なく、表示されている順序 (Balto、Hachiko、Lassie、Whitefang) で格納されます。詳細は、"グローバルの使用法" の “グローバル・ノードの照合” を参照してください。
条件付きループでの反復処理
前のセクションでは、一連の子ノードに対して 1 つのパスを作成する簡単な方法を示しましたが、簡易な foreach ループで提供できる以上の制御が必要になる場合があります。このセクションでは、反復子をより詳細に制御し、データに容易にアクセスできるメソッドとプロパティをいくつか示します。
-
IRISIterator.MoveNext() は、System.Collections.IEnumerator を実装して、反復子がいつ次のノードに移動するかを正確に制御できるようにします。次のノードが見つかった場合は true を返し、現在の反復処理にこれ以上ノードがない場合は false を返します。
-
IRISIterator.Reset() をループ終了後に呼び出して、反復子を開始位置にリセットし、再び使用できるようにすることができます。
-
IRISIterator.Current は、現在の反復子位置にあるノードの値を含むオブジェクトを取得します。これは、foreach ループで現在のループ変数に割り当てられている値と同じです。
-
IRISIterator.CurrentSubscript は、現在の反復子位置にあるノードの最下位の添え字を含むオブジェクトを取得します。例えば、反復子がノード myGlobal(23,"somenode") を指す場合、返されるオブジェクトには値 "somenode" が含まれます。
前の例と同じように、この例でも heroes グローバル配列を使用し、heroes("dogs") の下の子ノードを反復処理します。ただし、この例では、同じ反復子を使用して、子ノードで何度かパスを実行し、特定の条件が満たされたらすぐにループを終了します。
リストの項目に一致する値の検索
この例では、特定のノード値が見つかるか、ノードがなくなるまで、heroes("dogs") の下の子ノードをスキャンします。配列 targetDates は、メインの foreach ループで使用される targetYear 値のリストを指定します。メイン・ループ内で、do while ループは、各子ノードを検索して、その値を現在の targetYear と比較します。
IRISIterator iterDogs = iris.GetIRISIterator("heroes","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
このコードによって、以下のような出力が生成されます。
Whitefang was born in 1906
Lassie was born in 1940
Could not find a dog born in 2001
この例は、以下を実行します。
-
GetIRISIterator() は、heroes("dogs") の子ノードを照合順に検索する、反復子 iterDogs を返します (前のセクションの “一連の子ノードにわたる反復” に示されています)。iterDogs はリセットされ、foreach ループの各パスで再び使用されます。
-
MoveNext() は、次の子ノードを検索するために、do while ループの各パスで呼び出されます。ノードが見つかった場合、seek は true に設定され、これ以上子コードがない場合は false に設定されます。seek が false の場合、現在の targetYear 値が見つからなかったことを示すメッセージを出力した後、do while ループは終了します。
-
子ノードが見つかるたびに、iterDogs の Current および CurrentSubscript プロパティが設定されます。Current には現在のノード値が含まれ、CurrentSubscript には現在の添え字が含まれます。
-
Current は、targetYear と比較されます。一致がある場合、メッセージに添え字とノード値の両方が表示され、seek を false に設定して、do while ループは終了します。
-
Reset() は、各 do while パスの最後に呼び出されます。これにより、反復子 iterDogs は元の開始条件に戻され、次のパスで再び使用できるようになります。
子ノードおよびノード値のテスト
前の例では、検索の範囲は heroes("dogs") の子ノードに制限されています。グローバル配列 heroes の以下の 2 つの値は異なる親の下にあるため、反復子はこれらの値を見つけられません。
-
レベル 3 ノード heroes("dogs","Lassie","Timmy") は、dogs ではなく Lassie の子であるため、見つかりません。
-
レベル 2 ノード heroes("sheep","Dolly") は、dogs ではなく sheep の子であるため、見つかりません。
グローバル配列全体を検索するには、子ノードを持つすべてのノードを見つけて、それぞれに反復子を作成する必要があります。IsDefined() メソッドは、必要な情報を提供します。
以下の例は、2 つのメソッドで構成されます。
-
TestNode() は、heroes グローバル配列のノードごとに呼び出されます。現在のノードで IsDefined() を呼び出して、ノードに子ノードがあるかどうかを示すブーリアン値を返します。現在の添え字が Timmy または Dolly であるかも確認し、そうである場合はメッセージを出力します。
-
FindAllHeroes() は、TestNode() の返り値を使用して、グローバル配列全体を操作します。まず、ルート・ノード heroes の子ノードを反復処理します。TestNode() によって現在のノードに子ノードがあると示された場合、FindAllHeroes() は新しい反復子を作成して、下位レベルの子ノードをテストします。
メソッド FindAllHeroes()
この例では、既知の構造を処理して、入れ子になった単純な呼び出しを使用してさまざまなレベルを検索します。構造に任意の数のレベルがあるようなあまり一般的でない場合には、再帰アルゴリズムを使用できます。
public void FindAllHeroes() {
string root = "heroes";
// Iterate over child nodes of root node heroes
IRISIterator iterRoot = iris.GetIRISIterator(root);
foreach (object node in iterRoot) {
object sub1 = iterRoot.CurrentSubscript;
bool hasChild1 = TestNode(iterRoot,sub1);
// Process current child of heroes(sub1)
if (hasChild1) {
IRISIterator iterOne = iris.GetIRISIterator(root,sub1);
foreach (object node in iterOne) {
object sub2 = iterOne.CurrentSubscript;
bool hasChild2 = TestNode(iterOne,sub1,sub2);
// Process current child of heroes(sub1,sub2)
if (hasChild2) {
IRISIterator iterTwo = iris.GetIRISIterator(root,sub1,sub2);
foreach (object node in iterTwo) {
object sub3 = iterTwo.CurrentSubscript;
TestNode(iterTwo,sub1,sub2,sub3); //no child nodes below level 3
}
} //end hasChild2
}
} //end hasChild1
} // end main loop
} // end FindAllHeroes()
メソッド TestNode()
public bool TestNode(IRISIterator iter, string root, params object[] subscripts) {
// Test for values and child nodes
int state = iris.IsDefined(root,subscripts);
bool hasValue = (state%10 > 0); // has value if state is 1 or 11
bool hasChild = (state > 9); // has child if state is 10 or 11
// Look for lost heroes
// string[] lost = {"Timmy","Dolly"};
var lost = new List<string> {"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) {
if (lost.Contains(name))
Console.WriteLine("Hey, we found " + name + " (born in " + year + ")!!!");
// }
// }
}
return hasChild;
}