Skip to main content

This is documentation for Caché & Ensemble. See the InterSystems IRIS version of this content.Opens in a new tab

For information on migrating to InterSystems IRISOpens in a new tab, see Why Migrate to InterSystems IRIS?

eXTreme Event Persistence の使用法

Caché eXTreme Event Persistence (XEP) は、.NET 構造化データの非常に高速な格納および取得を可能にします。XEP は、複雑なデータ構造の最適なマッピングのためのスキーマ生成を制御する方法を提供しますが、単純なデータ構造のスキーマは、多くの場合、変更することなく生成および使用できます。XEP ではプロセス内通信と TCP/IP 接続のどちらも使用できます。

この章で説明する項目は以下のとおりです。

  • Event Persistence の概要 — 永続イベントの概念および用語について紹介し、XEP API を使用するコードの簡単な例を示します。

  • EventPersister の作成と接続EventPersister クラスのインスタンスを作成する方法、およびそれを使用して eXTreme または TCP/IP データベース接続を開き、テストし、閉じる方法について説明します。

  • スキーマのインポート — .NET クラスを分析するため、および対応する Caché イベントのスキーマを生成するためのメソッドおよび属性について説明します。

  • イベントの格納と変更 — 永続イベントの格納、変更、および削除に使用する Event クラスのメソッドについて説明します。

  • クエリの使用法 — クエリ結果セットを作成および処理する EventQuery<> クラスのメソッドについて説明します。

  • XEP からの Caché メソッドの呼び出し — XEP アプリケーションから ObjectScript メソッド、関数、およびプロシージャを呼び出すことができる EventPersister メソッドについて説明します。

  • スキーマのマッピングとカスタマイズ — .NET クラスが Caché イベント・スキーマにどのようにマップされるかについて詳しく説明するほか、最適なパフォーマンスを得るためのカスタマイズ・スキーマの生成方法についても詳細に説明します。

Event Persistence の概要

永続イベントは、.NET オブジェクトにデータ・フィールドの永続コピーを格納する Caché データベース・オブジェクトです。既定では、eXTreme Event Persistence API は、各イベントを通常の %PersistentOpens in a new tab オブジェクトとして格納します。ストレージは、その他の手段 (オブジェクト、SQL、直接グローバル・アクセスなど) でデータが Caché にアクセスできるように自動的に構成されます。

永続イベントを作成して格納するには、事前に XEP が対応する .NET クラスを分析して、スキーマインポートする必要があります。このスキーマは、.NET オブジェクトのデータ構造を Caché 永続イベントにどのように投影するのかを定義します。スキーマは、以下の 2 つのオブジェクト・プロジェクション・モデルのうちのいずれかを使用できます。

  • 既定のモデルは、フラット・スキーマです。そのスキーマでは、参照オブジェクトがすべてシリアル化されて、インポートされたクラスの一部として格納され、スーパークラスから継承したフィールドはすべて、それらが、インポートされたクラスのネイティブ・フィールドであるかのように格納されます。これは、最も高速かつ効率的なモデルですが、元の .NET クラス構造に関する情報は何も保持されません。

  • 構造情報を保持する必要がある場合は、フル・スキーマ・モデルを使用できます。このモデルでは、.NET ソース・クラスと Caché の投影されたクラスとの間に 1 対 1 のリレーションシップを作成することで、完全な .NET 継承構造が保持されますが、パフォーマンスがわずかに劣化することがあります。

両方のモデルの詳細は、“スキーマ・インポート・モデル” を参照してください。

スキーマがインポートされると、XEP を使用して非常に高いレートでデータの格納、照会、更新、および削除を行えます。格納されたイベントは、照会に対して、あるいは完全オブジェクトまたはグローバル・アクセスに対してすぐに使用できます。EventPersisterEvent、および EventQuery<> クラスは、XEP API のメイン機能を提供します。これらのクラスは、以下の順序で使用されます。

  • EventPersister クラスは、データベース接続を確立して制御するメソッドを提供します (“EventPersister の作成と接続” を参照してください)。

  • 接続が確立されたら、他の EventPersister メソッドを使用して、スキーマをインポートできます (“スキーマのインポート” を参照してください)。

  • Event クラスは、イベントの格納、更新、または削除、クエリの作成、インデックス更新の制御を行うメソッドを提供します (“イベントの格納と変更” を参照してください)。

  • EventQuery<> クラスは、データベースから一連のイベントを取得する単純な SQL クエリを実行するために使用されます。このクラスは、結果セットを繰り返し処理し、個別のイベントの更新および削除を行うためのメソッドを提供します (“クエリの使用法” を参照してください)。

次のセクション (“永続イベントを格納および照会する単純なアプリケーション” を参照) では、これらの機能をすべて例示する 2 つの非常に簡潔なアプリケーションについて説明します。

XEP は、スキーマのインポート時に .NET クラスを分析して基本情報を取得します。追加情報を提供して、XEP がインデックスを生成して、フィールドのインポートに関する既定のルールを上書きできるようにすることができます (“スキーマのカスタマイズ” を参照してください)。

永続イベントのフィールドは、単純な数値型およびそれらに関連する System 型、文字列、オブジェクト (組み込み/連続オブジェクトとして投影される)、列挙、およびコレクション・クラスから派生した型にすることができます。これらのデータ型を配列、入れ子になったコレクション、および配列のコレクションに含めることもできます。詳細は、“スキーマ・マッピング・ルール” を参照してください。

永続イベントを格納および照会する単純なアプリケーション

このセクションでは、XEP を使用して永続イベントを作成し、それにアクセスする 2 つの非常に単純なアプリケーションについて説明します。

  • StoreEvents プログラム — Caché データベースへの eXTreme 接続を開き、イベントを格納するためのスキーマを作成し、Event のインスタンスを使用してオブジェクトの配列を永続イベントとして格納し、接続を閉じて終了します。

  • QueryEvents プログラムStoreEvents と同じネームスペースにアクセスする新しい eXTreme 接続を開き、EventQuery<> のインスタンスを作成して前に格納されたイベントを読み取りおよび削除し、接続を閉じて終了します。

Note:

これらのアプリケーションは、システムを排他的に使用し、2 つの連続したプロセスで実行されることが前提となっています。

どちらのプログラムも、XEP サンプル・プログラムで定義されているクラスの 1 つである xep.samples.SingleStringSample のインスタンスを使用します (サンプル・プログラムの詳細は “XEP のサンプル” を参照してください)。

StoreEvents プログラム

StoreEvents では、EventPersister の新しいインスタンスが作成され、Caché サーバ上で特定のネームスペースに接続されます。SingleStringSample クラス用にスキーマがインポートされ、テスト・データベースが、そのクラスのエクステントから既存のイベントをすべて削除することで初期化されます。Event のインスタンスが作成され、SingleStringSample オブジェクトの配列を永続イベントとして格納するために使用されます。その後、接続が終了されます。その新しいイベントは Caché データベース内で永続化され、QueryEvents プログラムによってアクセスされるようになります (次のセクションで説明します)。

StoreEvents プログラム : スキーマの作成とイベントの格納
using System;
using InterSystems.XEP;
using xep.samples; // compiled XEPTest.csproj

public class StoreEvents {
  private static String className = "xep.samples.SingleStringSample";
  private static SingleStringSample[] eventData = SingleStringSample.generateSampleData(12);

  public static void Main(String[] args) {
    for (int i=0; i < eventData.Length; i++) {
      eventData[i].name = "String event " + i;
    }
    try {
      Console.WriteLine("Connecting and importing schema for " + className);
      EventPersister myPersister = PersisterFactory.CreatePersister();
      myPersister.Connect("User", "_SYSTEM", "SYS");
      try { // delete any existing SingleStringSample events, then import new ones
        myPersister.DeleteExtent(className); 
        myPersister.ImportSchema(className); 
      }
      catch (XEPException e) { Console.WriteLine("import failed:\n" + e); }
      Event newEvent = myPersister.GetEvent(className);
      long[] itemIDs = newEvent.Store(eventData);  // store array of events
      Console.WriteLine("Stored " + itemIDs.Length + " of " 
        + eventData.Length + " objects. Closing connection...");
      newEvent.Close();
      myPersister.Close();
    }
    catch (XEPException e) { Console.WriteLine("Event storage failed:\n" + e); }
  } // end Main()
} // end class StoreEvents

StoreEvents.Main() が呼び出される前に、xep.samples.SingleStringSample.generateSampleData() メソッドが呼び出されて、サンプル・データ配列 eventData が生成されます (サンプル・クラスの詳細は “XEP のサンプル” を参照してください)。

この例では、XEP メソッドは以下のアクションを実行します。

  • PersisterFactory.CreatePersister() は、EventPersister の新しいインスタンスである myPersister を作成します。

  • EventPersister.Connect() は、User ネームスペースへの eXTreme プロセス内接続を確立します。

  • EventPersister.ImportSchema() は、SingleStringSample クラスを分析し、そのスキーマをインポートします。

  • EventPersister.DeleteExtent() を呼び出すと、SingleStringSample エクステントから既存のテスト・データを削除することで、データベースがクリーン・アップされます。

  • EventPersister.GetEvent() は、SingleStringSample イベントの処理に使用される Event の新しいインスタンスである newEvent を作成します。

  • Event.Store() は、入力として eventData 配列を受け入れ、その配列内のオブジェクトごとに新しい永続イベントを作成します。(代わりに、コードで eventData 配列をループして個々のオブジェクトごとに Store() を呼び出すこともできますが、この例ではそのようにする必要はありません。)

  • Event.Close() および EventPersister.Close() は、イベントが格納された後に newEvent および myPersister に対して呼び出されます。これは、ネイティブ・コード・リソースを解放し、メモリ・リークを回避するために常に必要です。

これらのメソッドについては、すべてこの章で後で詳しく説明します。eXTreme 接続を開き、テストし、閉じることに関する詳細は、“EventPersister の作成と接続” を参照してください。スキーマの作成の詳細は、“スキーマのインポート” を参照してください。Event クラスの使用とエクステントの削除の詳細は、“イベントの格納と変更” を参照してください。

QueryEvents プログラム

この例では、StoreEvents プロセスの終了後、直ちに QueryEvents が実行されることを前提としています (“StoreEvents プログラム” を参照してください)。QueryEvents は、StoreEvents と同じネームスペースにアクセスする新しいデータベース接続を確立します。EventQuery<> のインスタンスが作成され、前に格納されたイベントに繰り返し処理を行い、それらのデータを出力し、それらを削除します。

QueryEvents プログラム : 永続イベントのフェッチと処理
using System;
using InterSystems.XEP;
using SingleStringSample = xep.samples.SingleStringSample; // compiled XEPTest.csproj

public class QueryEvents {
  public static void Main(String[] args) {
    EventPersister myPersister = null;
    EventQuery<SingleStringSample> myQuery = null;
    try {
// Open a connection, then set up and execute an SQL query
      Console.WriteLine("Connecting to query SingleStringSample events");
      myPersister = PersisterFactory.CreatePersister();
      myPersister.Connect("User","_SYSTEM","SYS");
      try {
        Event newEvent = myPersister.GetEvent("xep.samples.SingleStringSample");
        String sql = "SELECT * FROM xep_samples.SingleStringSample WHERE %ID BETWEEN 3 AND ?";
        myQuery = newEvent.CreateQuery<SingleStringSample>(sql);
        newEvent.Close();
        myQuery.AddParameter(12);  // assign value 12 to SQL parameter
        myQuery.Execute();
      }
      catch (XEPException e) {Console.WriteLine("createQuery failed:\n" + e);}

// Iterate through the returned data set, printing and deleting each event
      SingleStringSample currentEvent;
      currentEvent = myQuery.GetNext(); // get first item
      while (currentEvent != null) {
        Console.WriteLine("Retrieved " + currentEvent.name);
        myQuery.DeleteCurrent();
        currentEvent = myQuery.GetNext(); // get next item
      }
      myQuery.Close();
      myPersister.Close();
    }
    catch (XEPException e) {Console.WriteLine("QueryEvents failed:\n" + e);}
  } // end Main()
}  // end class QueryEvents

この例では、XEP メソッドは以下のアクションを実行します。

  • EventPersister.CreatePersister() および EventPersister.Connect() が再び呼び出され (StoreEvents の場合と同様)、User ネームスペースへの新しい接続が確立されます。

  • EventPersister.GetEvent() は、SingleStringSample エクステントに対するクエリの作成に使用される Event の新しいインスタンスである newEvent を作成します。クエリが作成された後、newEvent は、その Close() メソッドを呼び出すことで破棄されます。

  • Event.CreateQuery() は、SingleStringSample イベントの EventQuery<> のインスタンスである myQuery を作成します。SQL 文は、3 と変数パラメータ値の間のオブジェクト ID を持つ永続 SingleStringSample イベントすべてを取得するクエリを定義します。

  • EventQuery<>.AddParameter() は、値 12 を SQL パラメータに割り当てます。

  • EventQuery<>.Execute() はこのクエリを実行します。クエリが正常に実行された場合、myQuery に、そのクエリに一致する SingleStringSample イベントすべてのオブジェクト ID をリストする結果セットが含まれるようになります。

  • EventQuery<>.GetNext() が呼び出され、結果セットの最初の項目がフェッチされ、それが変数 currentEvent に割り当てられます。

  • while ループ内で以下のように実行されます。

    • currentEventname フィールドが出力されます。

    • EventQuery<>.DeleteCurrent() がデータベースから最後にフェッチされたイベントを削除します。

    • EventQuery<>.GetNext() が再び呼び出され、次のイベントがフェッチされ、それが変数 currentEvent に割り当てられます。

    これ以上項目がない場合、GetNext()null を返し、ループが終了します。

  • EventQuery<>.Close() および EventPersister.Close() は、すべてのイベントが出力および削除された後に myQuery および myPersister に対して呼び出されます。これは、ネイティブ・コード・リソースを解放し、メモリ・リークを回避するために常に必要です。

これらのメソッドについては、すべてこの章で後で詳しく説明します。eXTreme 接続を開き、テストし、閉じることに関する詳細は、“EventPersister の作成と接続” を参照してください。EventQuery<> のインスタンスの作成および使用の詳細は、“クエリの使用法” を参照してください。

EventPersister の作成と接続

EventPersister クラスは、XEP API の主なエントリ・ポイントです。その API は、データベースへの接続、スキーマのインポート、トランザクションの処理、および Event のインスタンスの作成を行い、データベース内のイベントにアクセスするためのメソッドを提供します。

EventPersister のインスタンスは、次のメソッドによって作成され、破棄されます。

  • PersisterFactory.CreatePersister()EventPersister の新しいインスタンスを返します。

  • EventPersister.Close() — この EventPersister インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。

以下のメソッドを使用して、接続を作成します。

  • EventPersister.Connect()namespaceusernamepasswordString 引数を取り、指定した Caché ネームスペースへの eXTreme プロセス内接続を確立します。

    オプションで、追加の host および port 引数を取り、eXTreme 接続の代わりに TCP/IP 接続を確立します。

1 つのプロセスで存在できる eXTreme 接続は 1 つのみであり、プロセス内の接続されたすべてのオブジェクトはその接続インスタンスを参照することを理解することが重要です。例えば、同じプロセス内の Xep.EventPersister オブジェクトと Globals.Connection オブジェクトは、基礎となる同じ接続を共用します。

以下の例は、プロセス内 eXTreme 接続を確立します。

EventPersister の作成と接続 : eXTreme 接続の作成
// Open an eXTreme connection
  String namespc = "USER";
  String username = "_SYSTEM";
  String password = "SYS";
  EventPersister myPersister = PersisterFactory.CreatePersister();
  myPersister.Connect(namespc, username, password);
  // perform event processing here . . .
  myPersister.Close();

PersisterFactory.CreatePersister() メソッドは、EventPersister の新しいインスタンスを作成します。1 つのプロセスで必要なインスタンスは 1 つのみです。

EventPersister.Connect() メソッドは、プロセス内 eXTreme 接続を確立します。現在のプロセスに接続が存在しない場合、新しい eXTreme 接続が作成されます。接続が既に存在している場合は、そのメソッドによって既存の接続オブジェクトへの参照が返されます。

アプリケーションを終了する準備が整ったら、EventPersister.Close() メソッドを常に呼び出して、基盤となるネイティブ・コードによって使用されたリソースを解放する必要があります。

Important:
常に Close() を呼び出してメモリ・リークを回避

EventPersister のインスタンスが範囲外になる前に、そのインスタンスで必ず Close() を呼び出すことが重要です。それを閉じないと、深刻なメモリ・リークが発生することがあります。それは、.NET ガーベッジ・コレクションでは、基盤となるネイティブ・コードによって割り当てられたリソースを解放できないためです。

TCP/IP 接続の確立

XEP アプリケーションが別のマシン上のデータベースにアクセスする必要がある場合、プロセス内 eXTreme 接続ではなく標準の TCP/IP 接続を確立するほうが適していることがあります。TCP/IP 接続のほうが少し低速ですが、その相違は、格納されるイベントの複雑さによって異なります。フィールドが 1 つまたは 2 つのみの非常に単純なイベントでは、eXTreme 接続のほうが大幅に高速です。より複雑なイベントの場合、相違はさほど明確ではなく、非常に複雑なイベントでは無視できる程度である可能性があります。

TCP/IP 接続は、以下の例に示すように Connect() の呼び出しでオプションの host および port 引数が指定されている場合に確立されます。

EventPersister の作成と接続 : TCP/IP 接続の作成
// Open a TCP/IP connection
  string host = "127.0.0.1";
  int port = 1972;
  myPersister.Connect(host, port, "User", "_SYSTEM", "SYS");
  // perform event processing here . . .
  myPersister.Close();

EventPersister.Connect() メソッドが host および port 引数、およびその後に前の例と同じ namespaceusername、および password 引数を指定して呼び出されます (この例ではハードコードされています)。これにより、指定されたホスト・マシンの指定されたポートへの TCP/IP 接続が確立されます。

基礎となる接続へのアクセス

XEP は、グローバル API の上にある層であり、同じ基礎となる接続を使用します。次のメソッドは、基礎となる eXTreme および DbConnection 接続を返します。

  • EventPersister.GetConnection() — 基礎となる eXTreme Globals.Connection オブジェクトを返します。接続が eXTreme ではなく、TCP/IP である場合は例外をスローします。

  • EventPersister.GetAdoNetConnection() — 基礎となる .NET DbConnection オブジェクトを返します。

eXTreme Connection オブジェクトは、このドキュメントに記載されている他の API も使用する XEP アプリケーションで役立つ可能性があります。それは、それらすべてで同じ基礎となる接続が使用されるためです。

スキーマのインポート

.NET クラスのインスタンスを永続イベントとして格納するには、その前にそのクラスのスキーマをインポートする必要があります。スキーマによって、イベントが格納されるデータベース構造が定義されます。XEP は、フラット・スキーマフル・スキーマの 2 つの異なるスキーマ・インポート・モデルを提供しています。これらのモデル間の主な相違は、.NET オブジェクトが Caché イベントに投影される方法です。パフォーマンスが重要であり、イベント・スキーマが比較的単純である場合、フラット・スキーマが最適な選択になります。フル・オブジェクトは、より複雑なスキーマ用のより豊富な機能を提供しますが、パフォーマンスに影響を与える可能性があります。スキーマ・モデルと関連テーマの詳細は、“スキーマのマッピングとカスタマイズ” を参照してください。

以下のメソッドを使用して、.NET クラスを分析し、目的のタイプのスキーマをインポートします。

  • EventPersister.ImportSchema()フラット・スキーマ をインポートします。.dll ファイル名、完全修飾クラス名、またはクラス名の配列を指定する引数を取り、すべてのクラスおよび指定された場所で検出されたすべての依存関係をインポートします。正常にインポートされたすべてのクラスの名前を含む String 配列を返します。

  • EventPersister.ImportSchemaFull()フル・スキーマ をインポートします。ImportSchema() と同じ引数を取り、同じクラス・リストを返します。このメソッドによってインポートされるクラスは、ユーザ生成 IdKey を宣言する必要があります (“IdKey の使用法” を参照してください)。

  • Event.IsEvent() — 静的 Event メソッド。引数としてすべての型の .NET オブジェクトまたはクラス名を取り、指定されているオブジェクトが、有効な XEP イベント (“インポートされるクラスの要件” を参照) として投影可能かどうかを調べるためのテストを行い、それが有効でない場合は適切なエラーをスローします。

インポート・メソッドは、使用されるスキーマ・モデルを除いて同一です。以下の例は、単純なテスト・クラスおよびその依存クラスをインポートします。

スキーマのインポート : クラスとその依存関係のインポート

以下のクラスがインポートされます。

namespace test {
  public class MainClass {
    public MainClass() {}
    public String  myString;
    public test.Address  myAddress;
  }

  public class Address {
    public String  street;
    public String  city;
    public String  state;
  }
}

以下のコードは、IsEvent() を呼び出してメイン・クラス test.MainClass が投影可能であることを確認してから、ImportSchema() を使用してそのメイン・クラスをインポートします。test.MainClass がインポートされるときに、依存クラス test.Address も自動的にインポートされます。

  try {
    Event.IsEvent("test.MainClass"); // throw an exception if class is not projectable
    myPersister.ImportSchema("test.MainClass");
  }
  catch (XEPException e) {Console.WriteLine("Import failed:\n" + e);}

この例では、依存クラス test.Address のインスタンスは、シリアル化されて test.MainClass の他のフィールドとして同じ Caché オブジェクトに埋め込まれます。代わりに ImportSchemaFull() が使用された場合、格納された test.MainClass のインスタンスには、別の Caché クラス・エクステントに格納されている test.Address のインスタンスへの参照が含まれます。

イベントの格納と変更

クラスのスキーマがインポートされると (“スキーマのインポート” を参照)、Event のインスタンスを作成して、そのクラスのイベントを格納し、それらにアクセスできます。Event クラスは、永続イベントの格納、更新、または削除、そのクラス・エクステントに対するクエリの作成、インデックス更新の制御を行うメソッドを提供します。このセクションでは、以下の項目について説明します。

  • イベントの作成と格納Event のインスタンスの作成方法およびそれを使用して指定したクラスの永続イベントを格納する方法について説明します。

  • 格納されたイベントへのアクセス — 指定したクラスの永続イベントをフェッチ、変更、および削除する Event メソッドについて説明します。

  • インデックス更新の制御 — インデックス・エントリを更新するタイミングを制御することで処理の効率性を高めることができる Event メソッドについて説明します。

イベントの作成と格納

Event クラスのインスタンスは、以下のメソッドによって作成され、破棄されます。

  • EventPersister.GetEvent()className String 引数を取り、指定したクラスのイベントを格納し、それにアクセスできる Event のインスタンスを返します。オプションで、インデックス・エントリを更新する既定の方法を指定する indexMode 引数を取ります (詳細は、“インデックス更新の制御” を参照してください)。

    Note:
    ターゲット・クラス

    Event のインスタンスは、GetEvent() の呼び出しで className 引数によって指定されたクラスのイベントのみを格納、アクセス、または照会できます。この章では、クラス className を、ターゲット・クラスと呼びます。

  • Event.Close() — この Event インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。

以下の Event メソッドは、ターゲット・クラスの .NET オブジェクトを永続イベントとして格納します。

  • Store() — ターゲット・クラスの 1 つ以上のインスタンスをデータベースに追加します。引数として 1 つのイベント、または複数のイベントからなる 1 つの配列を取り、格納されているイベントごとに long データベース ID (データベース ID を返すことができない場合は 0) を返します。

    Important:

    イベントが格納されるときは、どのようなテストも行われず、既存のデータが変更されたり、上書きされることはありません。各イベントは、可能な限り速い速度でエクステントに追加されるか、指定されたキーを持つイベントが既にデータベースに存在する場合は暗黙的に無視されます。

以下の例では、ターゲット・クラスとして SingleStringSample を指定して Event のインスタンスを作成し、それを使用して .NET SingleStringSample オブジェクトの配列を永続イベントとして投影します。この例では、myPersister が既に作成されて接続されており、SingleStringSample クラスのスキーマがインポートされていることを前提としています。この実行方法の例は、“永続イベントを格納および照会する単純なアプリケーション” を参照してください。SingleStringSample クラスと、このクラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル” を参照してください。

イベントの格納と変更 : オブジェクトの配列の格納
  SingleStringSample[] eventItems = SingleStringSample.generateSampleData(12);
  try {
    Event newEvent = myPersister.GetEvent("xep.samples.SingleStringSample");
    long[] itemIdList = newEvent.Store(eventItems);  // store all events
    int itemCount = 0;
    for (int i=0; i < itemIdList.Length; i++) {
      if (itemIdList[i]>0) itemCount++;
    }
    Console.WriteLine("Stored " + itemCount + " of " + eventItems.Length + " events");
    newEvent.Close();
  }
  catch (XEPException e) { Console.WriteLine("Event storage failed:\n" + e); }
  • SingleStringSamplegenerateSampleData() メソッドは、12 個の SingleStringSample オブジェクトを生成し、それらを eventData という名前の配列に格納します。

  • EventPersister.GetEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。

  • Event.Store() メソッドが呼び出され、eventData 配列内の各オブジェクトをデータベース内の永続イベントとして投影します。

    このメソッドは、itemIdList という名前の配列を返します。この配列には、正常に格納された各イベントの場合は long オブジェクト ID が、格納できなかったオブジェクトの場合は 0 が含まれます。変数 itemCount は、itemIdList0 より大きい各 ID に対して 1 回インクリメントされ、その合計が出力されます。

  • ループが終了すると、Event.Close() メソッドが呼び出され、基盤となるネイティブ・コードによって使用されたリソースを解放します。

Important:
常に Close() を呼び出してメモリ・リークを回避

Event のインスタンスが範囲外になるか再使用される前に、これらのインスタンスで必ず Close() を呼び出すことが重要です。それらを閉じないと、深刻なメモリ・リークが発生することがあります。それは、.NET ガーベッジ・コレクションでは、基盤となるネイティブ・コードによって割り当てられたリソースを解放できないためです。

格納されたイベントへのアクセス

永続イベントが格納されると、そのターゲット・クラスの Event インスタンスが、イベントを読み取り、更新、および削除するための以下のメソッドを提供します。

  • DeleteObject() — 引数としてデータベース・オブジェクト ID または IdKey を取り、データベースから指定したイベントを削除します。

  • GetObject() — 引数としてデータベース・オブジェクト ID または IdKey を取り、指定したイベントを返します。

  • UpdateObject() — 引数としてデータベース・オブジェクト ID または IdKey およびターゲット・クラスの Object を取り、指定したイベントを更新します。

ターゲット・クラスが標準オブジェクト ID を使用する場合、それは long 値として指定されます (前のセクションで説明した Store() メソッドで返されるとおり)。ターゲット・クラスが IdKey を使用する場合、それは Object の配列として指定され、その配列の各項目はその IdKey を構成するフィールドの 1 つの値です (“IdKey の使用法” を参照してください)。

以下の例では、配列 itemIdList には、前に格納された SingleStringSample イベントのオブジェクト ID 値のリストが含まれています。例では、リストの最初の 6 個の永続イベントを適宜更新し、残りを削除します。

Note:

itemIdList 配列を作成した例は、“イベントの作成と格納” を参照してください。この例では、myPersister という名前の EventPersister インスタンスが既に作成され、データベースに接続されていることも前提としています。

イベントの格納と変更 : イベントのフェッチ、更新、および削除
  // itemIdList is a previously created array of SingleStringSample event IDs
  try {
    Event newEvent = myPersister.GetEvent("xep.samples.SingleStringSample");
    int itemCount = 0;
    for (int i=0; i < itemIdList.Length; i++) {
      try { // arbitrarily update events for first 6 Ids and delete the rest
        SingleStringSample eventObject = (SingleStringSample)newEvent.GetObject(itemIdList[i]);
        if (i<6) {
          eventObject.name = eventObject.name + " (id=" + itemIdList[i] + ")" + " updated!";
          newEvent.UpdateObject(itemIdList[i], eventObject);
          itemCount++;
        } else {
          newEvent.DeleteObject(itemIdList[i]); 
        }
      }
      catch (XEPException e) {Console.WriteLine("Failed to process event:\n" + e);}
    }
    Console.WriteLine("Updated " + itemCount + " of " + itemIdList.Length + " events");
    newEvent.Close();
  }
  catch (XEPException e) {Console.WriteLine("Event processing failed:\n" + e);}
  • EventPersister.GetEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。

  • 配列 itemIdList には、前に格納された SingleStringSample イベントのオブジェクト ID 値のリストが含まれています (itemIdList を作成した例は、“イベントの作成と格納” を参照してください)。

    ループでは、itemIdList の各項目が処理されます。最初の 6 つの項目は変更および更新され、残りの項目は削除されます。以下の処理が実行されます。

    • Event.GetObject() メソッドが、itemIdList[i] に指定されたオブジェクト ID を持つ SingleStringSample オブジェクトをフェッチして、これを変数 eventObject に割り当てます。

    • eventObject name フィールドの値が変更されます。

    • eventObject がリストの最初の 6 項目の 1 つである場合は、Event.UpdateObject() が呼び出されてデータベース内でこれが更新されます。そうでない場合は、Event.DeleteObject() が呼び出されて、データベースからオブジェクトが削除されます。

  • itemIdList のすべての ID が処理されると、ループが終了し、更新されたイベントの数を示すメッセージが表示されます。

  • Event.Close() メソッドが呼び出され、基盤となるネイティブ・コードによって使用されたリソースを解放します。

SingleStringSample クラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル” を参照してください。

簡単な SQL クエリによってフェッチされた永続イベントへのアクセス方法、およびその変更方法の詳細は、“クエリの使用法” を参照してください。

テスト・データの削除

テスト・データベースを初期化する際は、多くの場合、クラス全体を削除するか、指定されたエクステントのすべてのイベントを削除すると便利です。以下の EventPersister メソッドは、クラスおよびエクステントを Caché データベースから削除します。

  • DeleteClass() — 引数として className 文字列を取り、指定した Caché クラスを削除します。

  • DeleteExtent() — 引数として className 文字列を取り、指定したクラスのエクステントのすべてのイベントを削除します。

これらのメソッドは主としてテスト用に提供されているので、実際に使用するコードでは使用しないようにしてください。これらの用語の詳しい定義については、"Caché プログラミング入門ガイド" の “クラスとエクステント” を参照してください。

インデックス更新の制御

既定では、データベース内のイベントに対して動作する Event メソッドの 1 つの呼び出しを実行するときに、インデックスは更新されません (“格納されたイベントへのアクセス” を参照してください)。インデックスは非同期に更新され、更新は、すべてのトランザクションが完了し、Event インスタンスが閉じられた後にのみ実行されます。一意のインデックスに対して一意性のチェックは実行されません。

Note:

このセクションは、標準オブジェクト ID または生成された IdKey を使用するクラスにのみ適用されます (“IdKey の使用法” を参照してください)。ユーザ割り当て IdKey を持つクラスは、同期でのみ更新できます。

この既定のインデックス作成動作を変更する方法はいくつかあります。Event インスタンスが EventPersister.GetEvent() によって作成される場合 (“イベントの作成と格納” を参照)、オプションの indexMode パラメータを設定して既定のインデックス作成動作を指定できます。以下のオプションを使用できます。

  • Event.INDEX_MODE_ASYNC_ON — 非同期のインデックス作成を可能にします。これは、indexMode パラメータが指定されていない場合の既定の設定です。

  • Event.INDEX_MODE_ASYNC_OFFStartIndexing() メソッドが呼び出されない限り、インデックス作成は実行されません。

  • Event.INDEX_MODE_SYNC — インデックス作成は、エクステントが変更されるたびに実行されます。これは多数のトランザクションの場合は非効率的になる可能性があります。クラスにユーザ割り当て IdKey がある場合は、このインデックス・モードを指定する必要があります。

以下の Event メソッドを使用すると、ターゲット・クラスのエクステントに対する非同期インデックス更新を制御できます。

  • StartIndexing() — ターゲット・クラスのエクステントに対して非同期インデックス作成を開始します。インデックス・モードが Event.INDEX_MODE_SYNC である場合は、例外をスローします。

  • StopIndexing() — エクステントに対する非同期インデックス作成を停止します。Event インスタンスを閉じるときにインデックスを更新しないようにする場合は、Event.Close() を呼び出す前にこのメソッドを呼び出します。

  • WaitForIndexing()int timeout 値を引数として取り、非同期インデックス作成が完了するまで待機します。timeout 値は、待機する秒数を指定します (-1 の場合は永久に待機し、0 の場合は直ちに返します)。インデックス作成が完了した場合は true を返し、インデックス作成が完了する前に待機がタイムアウトした場合は false を返します。インデックス・モードが Event.INDEX_MODE_SYNC である場合は、例外をスローします。

クエリの使用法

Event クラスは、ターゲット・クラスのエクステントに対して制限付き SQL クエリを実行できる EventQuery<> のインスタンスを作成する方法を提供します。EventQuery<> メソッドは、SQL クエリの実行、クエリ結果セット内の個別の項目の取得、更新、または削除を行うために使用します。

以下の項目について説明します。

Note:

このセクションの例では、EventPersister オブジェクトの myPersister が既に作成されて接続されており、SingleStringSample クラスのスキーマがインポートされていることを前提としています。この実行方法の例は、“永続イベントを格納および照会する単純なアプリケーション” を参照してください。

クエリの作成と実行

以下のメソッドは、EventQuery<> のインスタンスを作成および破棄します。

  • Event.CreateQuery() — SQL クエリのテキストを含む String 引数を取り、EventQuery<E> のインスタンスが返されます。パラメータ E は、親 Event のターゲット・クラスです。

  • EventQuery<>.Close() — この EventQuery<> インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。

EventQuery<E> のインスタンスによって実行されるクエリは、指定した汎用タイプ E (クエリ・オブジェクトを作成した Event インスタンスのターゲット・クラス) の .NET オブジェクトを返します。EventQuery<> クラスによってサポートされるクエリは、以下に示すとおり、SQL の SELECT 文のサブセットです。

  • クエリは、SELECT 節、FROM 節、および (オプションで) WHEREORDER BY などの標準 SQL 節から構成される必要があります。

  • SELECT 節および FROM 節は、構文的に正当ですが、現実的にはクエリ実行中には無視されます。マップされているすべてのフィールドは、常に、ターゲット・クラス E のエクステントからフェッチされます。

  • SQL 式は、任意の型の配列、組み込みオブジェクト、または組み込みオブジェクトのフィールドのいずれも参照できません。

  • Caché システム生成オブジェクト ID は、%ID と呼ばれることがあります。先頭に % があるため、これはフィールドが呼び出した id と .NET クラス内で競合しません。

以下の EventQuery<> メソッドは、クエリを定義および実行します。

  • AddParameter() — この EventQuery<> と関連付けられた SQL クエリのパラメータを結合します。パラメータに結合する値を指定する引数として Object value を取ります。

  • Execute() — この EventQuery<> と関連付けられた SQL クエリを実行します。クエリが正常に実行された場合、この EventQuery<> には、後で説明するメソッドでアクセスできる結果セットが含まれます (“クエリ・データの処理” を参照してください)。

以下の例では、xep.samples.SingleStringSample エクステント内のイベントに対して単純なクエリを実行します (SingleStringSample クラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル” を参照してください)。

クエリの使用法 : クエリの作成と実行
  Event newEvent = myPersister.GetEvent("xep.samples.SingleStringSample");
  EventQuery<SingleStringSample> myQuery = null;
  String sql = 
    "SELECT * FROM xep_samples.SingleStringSample WHERE %ID BETWEEN ? AND ?";

  myQuery = newEvent.CreateQuery<SingleStringSample>(sql);
  myQuery.AddParameter(3);  // assign value 3 to first SQL parameter
  myQuery.AddParameter(12);  // assign value 12 to second SQL parameter
  myQuery.Execute();   // get resultset for IDs between 3 and 12

EventPersister.GetEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。

Event.CreateQuery() メソッドは、SQL クエリを実行してその結果セットを保持する myQuery という名前の EventQuery<> のインスタンスを作成します。sql 変数には、2 つのパラメータ値の間の ID を持つターゲット・クラス内のすべてのイベントを選択する SQL 文が含まれています。

EventQuery<>.AddParameter() メソッドが 2 回呼び出されて、2 つのパラメータに値を割り当てます。

EventQuery<>.Execute() メソッドが呼び出されると、ターゲット・クラスのエクステントに対して指定されたクエリが実行され、結果セットが myQuery に格納されます。

既定では、結果セットの各オブジェクトのすべてのデータがフェッチされ、各オブジェクトが完全に初期化されます。各オブジェクトでフェッチされるデータの量および型を制限するオプションについては、“フェッチ・レベルの定義” を参照してください。

クエリ・データの処理

クエリが実行された後、以下の EventQuery<> メソッドを使用して、クエリ結果セット内の項目にアクセスし、データベース内の対応する永続イベントを更新または削除できます。

  • GetNext() — ターゲット・クラスの次のオブジェクトを結果セットから返します。結果セットにそれ以上項目がない場合は、null を返します。

  • UpdateCurrent() — 引数としてターゲット・クラスのオブジェクトを取り、それを使用して GetNext() によって最後に返された永続イベントを更新します。

  • DeleteCurrent() — データベースから GetNext() によって最後に返された永続イベントを削除します。

  • GetAll()GetNext() を使用して結果セットからすべての項目を取得し、それらの項目を List で返します。更新または削除には使用できません。GetAll() および GetNext() は同じ結果セットにアクセスできません。一方のメソッドが呼び出されると、他方のメソッドは Execute() が再び呼び出されるまで使用できません。

Id または IdKey によって特定された永続イベントへのアクセス方法、およびその変更方法の詳細は、“格納されたイベントへのアクセス” を参照してください。

クエリの使用法 : クエリ・データの更新と削除
  myQuery.Execute();   // get resultset
  SingleStringSample currentEvent = myQuery.GetNext();
  while (currentEvent != null) {
    if (currentEvent.name.StartsWith("finished")) {
      myQuery.DeleteCurrent();   // Delete if already processed
    } else {
      currentEvent.name = "in process: " + currentEvent.name;
      myQuery.UpdateCurrent(currentEvent);    // Update if unprocessed
    }
    currentEvent = myQuery.GetNext();
  }
  myQuery.Close();

この例では、EventQuery<>.Execute() の呼び出しによって前の例で説明したクエリが実行されることが前提になっています (“クエリの作成と実行” を参照)。また、結果セットは myQuery に格納されます。結果セットの各項目は SingleStringSample オブジェクトです。

GetNext() の最初の呼び出しによって、結果セットから最初の項目が取得され、それが currentEvent に割り当てられます。

while ループで、以下の処理が結果セットの各項目に適用されます。

  • currentEvent.name が文字列 "finished" で始まる場合、DeleteCurrent() は対応する永続イベントをデータベースから削除します。

  • それ以外の場合、currentEvent.name の値が変更され、UpdateCurrent() が呼び出されます。これは currentEvent を引数として取り、これを使用してデータ・ベースの永続イベントを更新します。

  • GetNext() の呼び出しによって、結果セットから次の SingleStringSample オブジェクトが返され、それが currentEvent に割り当てられます。

ループが終了した後、Close() が呼び出され、myQuery に関連付けられたネイティブ・コード・リソースを解放します。

Important:
常に Close() を呼び出してメモリ・リークを回避

EventQuery<> のインスタンスが範囲外になるか再使用される前に、これらのインスタンスで必ず Close() を呼び出すことが重要です。それらを閉じないと、深刻なメモリ・リークが発生することがあります。それは、.NET ガーベッジ・コレクションでは、基盤となるネイティブ・コードによって割り当てられたリソースを解放できないためです。

フェッチ・レベルの定義

フェッチ・レベルは、クエリを実行するときに返されるデータの量を制御するために使用できる Event プロパティです。これは、基礎となるイベントが複雑で、かつイベント・データの小さいサブセットのみが必要な場合に特に便利です。

以下の EventQuery<> メソッドは、現在のフェッチ・レベルを設定し、返します。

  • GetFetchLevel()Event の現在のフェッチ・レベルを示す int を返します。

  • SetFetchLevel() — 引数として Event フェッチ・レベル列挙の値の 1 つを取り、そのフェッチ・レベルを Event に対して設定します。

以下のフェッチ・レベル値がサポートされます。

  • Event.OPTION_FETCH_LEVEL_ALL — これが既定です。すべてのデータがフェッチされ、返されるイベントは完全に初期化されます。

  • Event.OPTION_FETCH_LEVEL_DATATYPES_ONLY — データ型フィールドのみがフェッチされます。これには、すべての単純な数値型とそれらに関連付けられた System 型、文字列、および列挙が含まれます。その他のフィールドはすべて null に設定されます。

  • Event.OPTION_FETCH_LEVEL_NO_ARRAY_TYPES — 配列を除くすべてのタイプがフェッチされます。ディメンジョンに関係なく、配列タイプのすべてのフィールドは null に設定されます。すべてのデータ型、オブジェクト・タイプ (シリアル化タイプを含む) およびコレクションがフェッチされます。

  • Event.OPTION_FETCH_LEVEL_NO_COLLECTIONS — コレクション・クラスの実装を除くすべてのタイプがフェッチされます。

  • Event.OPTION_FETCH_LEVEL_NO_OBJECT_TYPES — オブジェクト・タイプを除くすべてのタイプがフェッチされます。シリアル化タイプもオブジェクト・タイプとみなされ、フェッチされません。すべてのデータ型、配列タイプ、およびコレクションがフェッチされます。

XEP からの Caché メソッドの呼び出し

以下の EventPersister メソッドは、Caché クラス・メソッドを呼び出します。

  • CallClassMethod() — 指定した ObjectScript クラス・メソッドを呼び出します。className および methodName に対して String 引数を取り、ほかにクラス・メソッドに渡される 0 個以上の引数を取ります。intlongdouble、または String 型である Object を返します。

  • CallBytesClassMethod() — 文字列値が byte[] のインスタンスとして返されること以外は CallClassMethod() と同一です。

  • CallListClassMethod() — 文字列値が ValueList のインスタンスとして返されること以外は CallClassMethod() と同一です。

  • CallVoidClassMethod() — 何も返されないこと以外は CallClassMethod() と同一です。

以下の EventPersister メソッドは Caché 関数およびプロシージャを呼び出します ("Caché ObjectScript の使用法" の “ユーザ定義コード” を参照してください)。

  • CallFunction() — 指定した ObjectScript 関数を呼び出します。functionName および routineName に対して String 引数を取り、ほかにこの関数に渡される 0 個以上の引数を取ります。intlongdouble、または String 型である Object を返します。

  • CallBytesFunction() — 文字列値が byte[] のインスタンスとして返されること以外は CallFunction() と同一です。

  • CallListFunction() — 文字列値が ValueList のインスタンスとして返されること以外は CallFunction() と同一です。

  • CallProcedure() — 指定した ObjectScript プロシージャを呼び出します。procedureName および routineName に対して String 引数を取り、ほかにこのプロシージャに渡される 0 個以上の引数を取ります。

スキーマのマッピングとカスタマイズ

このセクションでは、.NET クラスが Caché イベント・スキーマにどのようにマップされるか、また最適なパフォーマンスを得るためにスキーマをどのようにカスタマイズできるかについて詳細に説明します。ここで説明する内容は以下のとおりです。

  • スキーマ・インポート・モデル — XEP によってサポートされる 2 つのスキーマ・インポート・モデルについて説明します。

  • スキーマのカスタマイズ — インデックスの追加やフィールドのインポートに関する既定のルールの上書きによって、スキーマをカスタマイズするためのさまざまなオプションについて説明します。

  • スキーマ・マッピング・ルール — .NET クラスが Caché イベント・スキーマにどのようにマップされるかについて詳しく説明します。

スキーマ・インポート・モデル

XEP は、フラット・スキーマとフル・スキーマの 2 つの異なるスキーマ・インポート・モデルを提供しています。これらのモデル間の主な相違は、.NET オブジェクトが Caché イベントに投影される方法です。

  • 埋め込みオブジェクト・プロジェクション・モデル (フラット・スキーマ)フラット・スキーマ をインポートします。そのスキーマでは、インポートされたクラスによって参照されるオブジェクトがすべてシリアル化されて埋め込まれ、すべての祖先クラスで宣言されたフィールドはすべて、それらが、インポートされたクラス自体で宣言されているかのように収集され、投影されます。そのクラスのインスタンスのすべてのデータは、単一の Caché %Library.PersistentOpens in a new tab オブジェクトとして格納され、元の .NET クラス構造に関する情報は保持されません。

  • フル・オブジェクト・プロジェクション・モデル (フル・スキーマ)フル・スキーマ をインポートします。そのスキーマでは、インポートされたクラスによって参照されるオブジェクトがすべて別の Caché %PersistentOpens in a new tab オブジェクトとして投影されます。継承されたフィールドは、同様に Caché %PersistentOpens in a new tab クラスにインポートされている祖先クラスのフィールドへの参照として投影されます。.NET ソース・クラスと Caché の投影されたクラスの間には 1 対 1 の対応があるため、.NET クラス継承構造が保持されます。

フル・オブジェクト・プロジェクションでは、元の .NET クラスの継承構造が保持されますが、パフォーマンスに影響する場合があります。パフォーマンスが重要であり、イベント・スキーマが比較的単純である場合、フラット・オブジェクト・プロジェクションが最適な選択になります。パフォーマンスの低下を許容できる場合、より豊富な機能および複雑なスキーマのために、フル・オブジェクト・プロジェクションを使用できます。

埋め込みオブジェクト・プロジェクション・モデル (フラット・スキーマ)

既定では、XEP は、平坦化することで参照オブジェクトを投影するスキーマをインポートします。つまり、インポートされたクラスによって参照されるオブジェクトがすべてシリアル化されて埋め込まれ、すべての祖先クラスで宣言されたフィールドはすべて、それらが、インポートされたクラス自体で宣言されているかのように収集され、投影されます。対応する Caché イベントは、%Library.PersistentOpens in a new tab を拡張し、シリアル化されて埋め込まれたオブジェクト (元の .NET オブジェクトには外部オブジェクトへの参照が含まれていた) を含んでいます。

つまり、フラット・スキーマでは、厳密な意味での継承は Caché 側に保持されません。例えば、以下の 3 つの .NET クラスを考えてみます。

class A {
    String a;
}
class B : class A {
    String b;
}
class C : class B {
    String c;
}

クラス C をインポートすると、以下の Caché クラスができます。

Class C : %Persistent ... {
     Property a As %String;
     Property b As %String;
     Property c As %String;
}

対応する Caché イベントは、特別にインポートしない限り、AB のいずれのクラスに対しても生成されません。Caché 側のイベント C は、AB も拡張しません。インポートされると A および B は、以下の構造になります。

Class A : %Persistent ... {
     Property a As %String;
}
Class B : %Persistent ... {
     Property a As %String;
     Property b As %String;
}

すべての処理は、対応する Caché イベントに対してのみ実行されます。例えば、タイプ C のオブジェクトに対して Store() を呼び出すと、対応する C Caché イベントのみが格納されます。

.NET の子クラスが、そのスーパークラスでも宣言される同じ名前のフィールドを非表示にすると、XEP エンジンは常にその子フィールドの値を使用します。

フル・オブジェクト・プロジェクション・モデル (フル・スキーマ)

フル・オブジェクト・モデルでは、一致する継承構造を Caché に作成することで、.NET 継承モデルを保持するスキーマをインポートします。すべてのオブジェクト・フィールドをシリアル化してすべてのデータを単一の Caché オブジェクトに格納するのではなく、スキーマによって .NET ソース・クラスと Caché の投影されたクラスとの間に 1 対 1 のリレーションシップが確立されます。フル・オブジェクト・プロジェクション・モデルでは、参照されるクラスがそれぞれ別々に格納され、指定されているクラスのフィールドが、対応する Caché クラスのオブジェクトへの参照として投影されます。

参照されるクラスには、ユーザ定義 IdKey ([Id] または [Index] — “IdKey の使用法” を参照) を作成する属性が含まれている必要があります。オブジェクトが格納されるときは、参照されるすべてのオブジェクトが最初に格納され、結果の IdKey がその親オブジェクトに格納されます。残りの XEP と同様に、一意性チェックは実行されず、既存のデータの変更または上書きは試みられません。データは単に、可能な限り速い速度で追加されます。IdKey 値が、既に存在しているイベントを参照している場合、それは単にスキップされ、その既存のイベントの上書きが試みられることはありません。

[Embedded] クラス・レベル属性を使用すると、マークされたクラスのインスタンスを別々に格納するのではなく、シリアル化されたオブジェクトとして埋め込むことで、フル・スキーマを最適化できます。

Note:

フル・オブジェクト・モデルの使用法の例は、FlightLog サンプル・プログラム (“XEP のサンプル” にリストされている) を参照してください。

スキーマのカスタマイズ

多くの場合、メタ情報を提供することなく、単純なクラスのスキーマをインポートできます。しかし、状況によって、スキーマのインポート方法のカスタマイズが必要となったり要望されたりすることがあります。次のセクションでは、インデックスの追加やフィールドのインポートに関する既定のルールの上書きによって、カスタマイズ・スキーマを生成するためのさまざまなオプションについて説明します。

  • 属性の使用法 — XEP 属性を .NET クラスに追加して、作成する必要があるインデックスを指定できます。また、アノテーションを追加して、インポートしないフィールドまたはシリアル化する必要があるフィールドを指定してパフォーマンスを最適化することもできます。

  • IdKey の使用法 — 属性を使用して、IdKey (既定のオブジェクト ID の代わりに使用するインデックス値) を指定できます。IdKey はフル・スキーマをインポートする場合に必要です。

  • InterfaceResolver の実装 — 既定では、インタフェースとして宣言されたフィールドはフラット・スキーマにインポートされません。スキーマのインポート時に、InterfaceResolver インタフェースの実装を使用して、インタフェースとして宣言されたフィールドの実際のクラスを指定することができます。

属性の使用法

XEP エンジンは、.NET クラスを調べることで、XEP イベント・メタデータを推測します。追加情報は、属性を使用して .NET クラスで指定できます。属性は Intersystems.XEP.attributes ネームスペースにあります。.NET オブジェクトは、XEP 永続イベントの定義に準拠している限り (“インポートされるクラスの要件” を参照)、Caché イベントとして投影され、カスタマイズは必要ありません。

属性には、投影されるクラス内の個別のフィールドに適用されるものと、クラス全体に適用されるものがあります。

  • フィールド属性 — インポートされるクラスのフィールドに適用されます。

    • [Id] — そのフィールドが IdKey として機能するようになることを指定します。

    • [Serialized] — フィールドをシリアル化形式で格納および取得する必要があることを示します。

    • [Transient] — フィールドをインポートから除外する必要があることを示します。

  • クラス属性 — インポートされるクラス全体に適用されます。

    • [Embedded] — フル・スキーマでこのクラスのフィールドを、参照ではなく (フラット・スキーマと同様に) 埋め込みとする必要があることを示します。

    • [Index] — クラスのインデックスを宣言します。

[Id] (フィールド・レベル属性)

[Id] でマークされたフィールドの値は、標準のオブジェクト ID を置換する IdKey として使用されます (“IdKey の使用法” を参照してください)。この属性は、クラスごとに 1 つのフィールドでのみ使用でき、そのフィールドは Stringint、または long である必要があります (double は許容されますが推奨されません)。複合 IdKey を作成するには、代わりに [Index] 属性を使用します。[Id] でマークされたクラスは、[Index] を使用して複合プライマリ・キーも宣言することはできません。両方の属性が同じクラスに対して使用されている場合、例外がスローされます。

以下のパラメータを指定する必要があります。

  • generated — XEP がキー値を生成する必要があるかどうかを指定する bool

    • generated = true — (既定の設定) キー値は Caché によって生成され、[Id] でマークされたフィールドは Int64 である必要があります。このフィールドは、挿入または保存の前は null であることが前提となっており、そのような操作の完了時に XEP によって自動的に入力されます。

    • generated=false — マークされたフィールドのユーザ割り当て値が IdKey 値として使用されます。フィールドは、StringintInt32long、または Int64 にできます。

次の例では、ssn フィールドのユーザ割り当て値がオブジェクト ID として使用されます。

using Id = InterSystems.XEP.Attributes.Id;
public class Person {
  [Id(generated=false)]
  Public String  ssn;
  public String  name;
  Public String dob;
}
[Serialized] (フィールド・レベル属性)

[Serialized] 属性は、フィールドをシリアル化形式で格納および取得する必要があることを示します。

この属性は、シリアル化可能フィールド (配列を含みます。これは暗黙的にシリアル化可能です) の格納を最適化します。XEP エンジンは、データの格納または取得のための既定のメカニズムを使用するのではなく、シリアル・オブジェクト用の関連する読み取りまたは書き込みメソッドを呼び出します。マークされたフィールドがシリアル化可能でない場合は、例外がスローされます。

例 :

using Serialized = InterSystems.XEP.Attributes.Serialized;
public class MyClass {
  [Serialized]
  public  xep.samples.Serialized   serialized;
  [Serialized]
  public  int[,,,]   quadIntArray;
  [Serialized]
  public  String[,]   doubleStringArray;
}

// xep.samples.Serialized:
[Serializable]
public class Serialized {
  public  String  name;
  public  int     value;
}
[Transient] (フィールド・レベル属性)

[Transient] 属性は、フィールドをインポートから除外する必要があることを示します。マークされたフィールドは、Caché に投影されず、イベントが格納される、またはロードされるときに無視されます。

例 :

using Transient = InterSystems.XEP.Attributes.Transient;
public class MyClass {
  // this field will NOT be projected:
  [Transient]
  public  String  transientField;

  // this field WILL be projected:
  public  String  projectedField;
}
[Embedded] (クラス・レベル属性)

[Embedded] 属性は、フル・スキーマが生成される場合に使用できます (“スキーマ・インポート・モデル” を参照してください)。Caché に投影するときに、このクラスのフィールドを、参照ではなく (フラット・スキーマと同様に) シリアル化して埋め込む必要があることを示します。

例 :

using Embedded = InterSystems.XEP.Attributes.Embedded;
[Embedded]
public class Address {
  String  street;
  String  city;
  String  zip;
  String  state;
}

[Index] (クラス・レベル属性)

[Index] 属性を使用すると、1 つ以上の複合インデックスを宣言できます。

以下のパラメータに引数を指定する必要があります。

  • name — 複合インデックスの名前を含む String

  • fields — 複合インデックスを構成するフィールドの名前を含む String の配列。

  • type — インデックス・タイプ。xep.attributes.IndexType 列挙には、以下の使用可能なタイプがあります。

    • IndexType.none — 既定値。インデックスがないことを示します。

    • IndexType.bitmap — ビットマップ・インデックス ("Caché SQL の使用法" の “ビットマップ・インデックス” を参照してください)。

    • IndexType.bitslice — ビットスライス・インデックス ("Caché SQL の使用法" の “概要” を参照してください)。

    • IndexType.simple — 1 つ以上のフィールドに対する標準インデックス。

    • IndexType.idkey — 標準 ID の代わりに使用されるインデックス (“IdKey の使用法” を参照してください)。

例 :

using Index = InterSystems.XEP.Attributes.Index;
using IndexType = InterSystems.XEP.Attributes.IndexType;

[Index(name="indexOne",fields=new string[]{"ssn","dob"},type=IndexType.idkey)]
public class Person {
  public String  name;
  public String dob;
  public String  ssn;
}

IdKey の使用法

IdKey は、既定のオブジェクト ID の代わりに使用されるインデックス値です。単純 IdKey と複合 IdKey のどちらも XEP によってサポートされており、フル・スキーマでインポートされる .NET クラスにはユーザ生成 IdKey が必要です (“スキーマのインポート” を参照してください)。単一フィールドに対する IdKey は [Id] 属性で作成できます。複合 IdKey を作成するには、IndexType idkey と共に [Index] 属性を追加します。例えば、以下のクラスを指定したとします。

  class Person {
    String name;
    int id;
    String dob;
  }

既定のストレージ構造では、添え字として標準オブジェクト ID を使用します。

^PersonD(1)=$LB("John",12,"1976-11-11")

以下の属性は、name および id フィールドを使用して、標準オブジェクト ID を置き換える newIdKey という名前の複合 IdKey を作成します。

  [Index(name="newIdKey", fields=new String[]{"name","id"},type=IndexType.idkey)]

これは、以下のグローバル構造になります。

^PersonD("John",12)=$LB("1976-11-11")

XEP では、SQL コマンドなど他の方法で追加された IdKey も処理されます ("Caché SQL の使用法" の “インデックスでの Unique、PrimaryKey、IDKey キーワードの使用” を参照してください)。XEP エンジンは、基本クラスに IdKey が含まれているかどうかを自動的に判別し、適切なグローバル構造を生成します。

IdKey の使用法にはいくつかの制限があります。

  • IdKey 値は一意である必要があります。IdKey がユーザ生成である場合、一意性は呼び出し元のアプリケーションで確保する必要があり、XEP によって強制されることはありません。アプリケーションで、データベースに既に存在しているキー値を持つイベントの追加が試みられると、その試みは暗黙的に無視され、既存のイベントは変更されません。

  • IdKey を宣言するクラスには、それが他のインデックスも宣言している場合、非同期にインデックスを付けることはできません。

  • 複合 IdKey ではフィールドの数に制限はありませんが、フィールドが Stringint、または long であるか、それらに対応する System 型であることが必要です。double も使用できますが、非推奨です。

  • 非常に高い継続的な挿入レートを必要とするまれな特定の状況では、パフォーマンスが低下することがあります。

IdKey に基づくイベントの取得、更新、および削除を可能にする Event メソッドの説明は、“格納されたイベントへのアクセス” を参照してください。

IdKey および標準 Caché ストレージ・モデルの詳細は、"Caché グローバルの使用法" の “多次元ストレージの SQL およびオブジェクトの使用法” を参照してください。SQL での IdKey の詳細は、"Caché SQL の使用法" の “インデックスの定義と構築” を参照してください。

サンプル・プログラム IdKeyTest および FlightLog は、IdKey の使用例を示しています (サンプル・プログラムの詳細は、“XEP のサンプル” を参照してください)。

InterfaceResolver の実装

フラット・スキーマがインポートされるとき、継承階層に関する情報は保持されません (“スキーマ・インポート・モデル” を参照してください)。これにより、インタフェースとして宣言されているタイプを持つフィールドを処理するときに問題が発生します。これは、XEP エンジンは、フィールドの実際のクラスを把握する必要があるためです。既定では、そのようなフィールドはフラット・スキーマにインポートされません。この動作は、Intersystems.XEP.InterfaceResolver の実装を作成し、処理中に特定のインタフェース・タイプを解決することで、変更できます。

Note:

InterfaceResolver は、フラット・スキーマ・インポート・モデルにのみ関連しており、.NET クラス継承構造は保持されません。フル・スキーマ・インポート・モデルでは、.NET と Caché のクラスの間に 1 対 1 のリレーションシップが確立されるため、インタフェースの解決に必要な情報が保持されます。

InterfaceResolver の実装は、フラット・スキーマ・インポート・メソッド ImportSchema() を呼び出す前に EventPersister に渡されます (“スキーマのインポート” を参照してください)。これにより、XEP エンジンに、処理中にインタフェース・タイプを解決する方法が提供されます。以下の EventPersister メソッドは、使用される実装を指定します。

  • EventPersister.SetInterfaceResolver() — 引数として InterfaceResolver のインスタンスを取ります。ImportSchema() が呼び出されると、指定されたインスタンスを使用して、インタフェースとして宣言されたフィールドを解決します。

以下の例は、クラスごとに InterfaceResolver の異なるカスタマイズされた実装を呼び出して、2 つの異なるクラスをインポートします。

スキーマのカスタマイズ : InterfaceResolver の適用
  try {
    myPersister.SetInterfaceResolver(new test.MyFirstInterfaceResolver());
    myPersister.ImportSchema("test.MyMainClass");

    myPersister.SetInterfaceResolver(new test.MyOtherInterfaceResolver());
    myPersister.ImportSchema("test.MyOtherClass");
  }
  catch (XEPException e) {Console.WriteLine("Import failed:\n" + e);}

SetInterfaceResolver() の最初の呼び出しによって、インポート・メソッドの呼び出し中に使用する実装として MyFirstInterfaceResolver (次の例で説明) の新しいインスタンスが設定されます。この実装は、SetInterfaceResolver() が再び呼び出されて別の実装が指定されるまで、ImportSchema() のすべての呼び出しで使用されます。

ImportSchema() の最初の呼び出しで、クラス test.MyMainClass がインポートされます。これには、インタフェース test.MyFirstInterface として宣言されたフィールドが含まれています。MyFirstInterfaceResolver のインスタンスが、そのインポート・メソッドで使用され、このフィールドの実際のクラスが解決されます。

SetInterfaceResolver() の 2 回目の呼び出しによって、ImportSchema() が再び呼び出されたときに使用する新しい実装として別の InterfaceResolver クラスのインスタンスが設定されます。

InterfaceResolver のすべての実装で、以下のメソッドを定義する必要があります。

  • InterfaceResolver.GetImplementationClass() は、インタフェースとして宣言されたフィールドの実際のデータ型を返します。このメソッドには以下のパラメータがあります。

    • interfaceClass — 解決されるインタフェース。

    • declaringClassinterfaceClass として宣言されたフィールドが含まれているクラス。

    • fieldName — インタフェースとして宣言された declaringClass のフィールドの名前が含まれている文字列。

次の例は、インタフェース、そのインタフェースの実装、およびそのインタフェースのインスタンスを解決する InterfaceResolver の実装を定義します。

スキーマのカスタマイズ : InterfaceResolver の実装

この例では、解決されるインタフェースは、test.MyFirstInterface です。

namespace test {
  public interface MyFirstInterface{ }
}

test.MyFirstImpl クラスは、InterfaceResolver によって返される必要がある test.MyFirstInterface の実装です。

namespace test {
  public class MyFirstImpl : MyFirstInterface {
    public MyFirstImpl() {}
    public MyFirstImpl(String s) { fieldOne = s; }
    public String  fieldOne;
  }
}

InterfaceResolver の以下の実装は、インタフェースが test.MyFirstInterface である場合はクラス test.MyFirstImpl を返し、それ以外の場合は null を返します。

using Intersystems.XEP;
namespace test {
  public class MyFirstInterfaceResolver : InterfaceResolver {
    public MyFirstInterfaceResolver() {}
    public Type GetImplementationType(Type declaringClass, 
           String fieldName, Type interfaceClass) {
      if (interfaceClass == typeof(test.MyFirstInterface)) {
        return typeof(test.MyFirstImpl);
      }
      return null;
    }
  }

MyFirstInterfaceResolver のインスタンスが SetInterfaceResolver() によって指定されている場合、ImportSchema() の後続の呼び出しでは自動的にそのインスタンスが使用されて、test.MyFirstInterface として宣言されているすべてのフィールドが解決されます。そのようなフィールドごとに、そのフィールドを含むクラスにパラメータ declaringClass を設定し、fieldName をそのフィールドの名前に設定し、interfaceClasstest.MyFirstInterface に設定して、GetImplementationClass() メソッドが呼び出されます。このメソッドは、インタフェースを解決し、test.MyFirstImplnull のいずれかを返します。

スキーマ・マッピング・ルール

このセクションでは、XEP スキーマがどのように構築されるかについて説明します。以下の項目について説明します。

  • インポートされるクラスの要件 — 永続イベントとして投影できるオブジェクトを作成するために .NET クラスが満たす必要がある構造ルールについて説明します。

  • 名前付け規約 — .NET クラスおよびフィールドの名前を Caché の名前付け規則に従うように変換する方法について説明します。

インポートされるクラスの要件

XEP スキーマ・インポート・メソッドは、以下の要件を満たさない限り、.NET クラスに対して有効なスキーマを作成できません。

  • インポートされる Caché クラスまたは派生クラスが、格納されたイベントにクエリを実行してそれにアクセスするために使用される場合、.NET ソース・クラスは引数のないパブリック・コンストラクタを明示的に宣言する必要があります。

  • .NET ソース・クラスは、Object として宣言されたフィールドと、Object を宣言の一部として使用する配列またはコレクションのいずれも含むことはできません。XEP エンジンがこのようなフィールドを検出すると、例外がスローされます。 [Transient] 属性 (“属性の使用法” を参照) を使用して、これらがインポートされないようにします。

Event.IsEvent() メソッドを使用して、.NET クラスまたはオブジェクトを分析し、それが XEP の意味で有効なイベントを作成できるかどうかを判定できます。前述の条件に加えて、以下の条件が検出された場合、このメソッドは XEPException をスローします。

  • 循環した依存関係

  • 決まった形式のないコレクション

  • String、単純な数値型またはそれに関連する System 型、列挙のいずれでもない Dictionary キー値。

永続イベントのフィールドは、単純な数値型またはそれらに関連する System 型、オブジェクト (組み込み/連続オブジェクトとして投影される)、列挙、およびコレクション・クラスから派生した型にすることができます。これらのデータ型を配列、入れ子になったコレクション、および配列のコレクションに含めることもできます。

既定では、投影されるフィールドは、.NET クラスのすべての機能を保持しない場合があります。特定のフィールドが以下の方法で変更されます。

  • .NET クラスに静的フィールドが含まれていても、これらは既定でプロジェクションから除外されます。対応する Caché プロパティはありません。追加のフィールドは、[Transient] 属性を使用して除外できます (“属性の使用法” を参照してください)。

  • フラット・スキーマ (“スキーマ・インポート・モデル” を参照) では、内部 (入れ子にされた) .NETクラスを含むすべてのオブジェクト・タイプは、%SerialObjectOpens in a new tab クラスとして Caché に投影されます。オブジェクト内のフィールドは、別の Caché プロパティとして投影されません。このオブジェクトは、ObjectScript の観点からは不明瞭です。

  • フラット・スキーマは、すべての継承されたフィールドを、それらが子クラスで宣言されたかのように投影します。

名前付け規約

対応する Caché クラスおよびプロパティの名前は .NET での名前と同じです。ただし、.NET では許可されているが Caché では許可されていない以下の 2 つの特別な文字は例外です。

  • $ (ドル記号) は、Caché 側では "d" の 1 文字に投影されます。

  • _ (アンダースコア) は、Caché 側では "u" の 1 文字に投影されます。

クラス名は 255 文字に制限されます。この文字数はほとんどのアプリケーションで十分のはずです。ただし、対応するグローバル名には 31 文字の制限があります。これは通常 1 対 1 マッピングには不十分なため、XEP エンジンは、31 文字よりも長いクラス名用に透過的に一意のグローバル名を生成します。生成されるグローバル名は元の名前と同じではありませんが、それでも簡単に認識できるはずです。例えば、xep.samples.SingleStringSample クラスは、グローバル名 xep.samples.SingleStrinA5BFD を受け取ります。

FeedbackOpens in a new tab