エンティティセレクションとは、同じデータクラスに所属するエンティティへの一つ以上の参照を格納しているオブジェクト のことです。エンティティセレクションは、データクラスから0個、1個、あるいはX個のエンティティを格納することができます。ここでのXはデータクラスに格納されているエンティティの総数を表します。
エンティティセレクションには以下の区別があります:
"共有可能"または"追加可能" "順列あり"あるいは"順列なし" この点については以下に説明があります。
エンティティセレクションは一般的にクエリを使用して作成されるか、あるいはリレーション属性から返されます。例:
brokers :=ds .Person.query ("personType = broker")
このコードではbrokers にbroker タイプの人間が全て返されます。セレクションのエンティティにアクセスするためには、コレクション内の要素を昇順に調べるのに似ているシンタックスを使用します。例:
theBroker:=brokers[0]
entitySelection.orderBy( ) メソッドは提供されたソート条件に基づいた新しいエンティティセレクションを返します。例:
brokers:=brokers.orderBy ("name")
このコードはbrokers にPersonエンティティからのエンティティセレクションを返しますが、名前順で並べ替えされたものが返されます。
また、リレーション属性を使用してエンティティセレクションを返すことも可能です。例:
brokers :=ds .Person.query ("personType = broker")
brokerCompanies :=brokers .myCompany
このコードは、myCompany リレーション属性を使用して、brokers エンティティセレクション内の人間にリレートされた全ての企業をbrokerCompanies に代入します。エンティティセレクションに対してリレーション属性を使用するのは、リレートされたエンティティの連鎖を上下に検索するための、強力かつ簡単な方法です。
エンティティセレクション内のエンティティに対して何か繰り返しの操作を実行する(例えば特定の属性の値を取得して変更する)場合、For each...End for each 構造を使用することができます。例:
C_OBJECT (emp )
For each (emp;ds .Employees.all ())
If (emp.Country="UK")
emp.salary:=emp.salary*1,03
emp.save ()
End if
End for each
以下の方法を用いることで、エンティティセレクション型のオブジェクトを作成することができます:
データクラスに対して、異なるエンティティセレクションを好きなだけ同時に作成し、使用することができます。エンティティセレクションは、エンティティへの参照を格納しているに過ぎないという点に注意してください。異なるエンティティセレクションが同じエンティティへの参照を格納することも可能です。
注: エンティティセレクションはそれが作成されたプロセスにおいてのみ定義されます。ですから、例えばエンティティセレクションへの参照をインタープロセス変数に保存して、他のプロセスで使用するという使い方はできません。
注: エンティティセレクションは、それが共有可能 であった場合には異なるプロセスで使用することができます。詳細な情報については、以下の共有可能なエンティティセレクション/追加可能のエンティティセレクション の段落を参照してください。
全てのストレージ属性(テキスト、数値、ブール、日付)はエンティティセレクションのプロパティ、あるいはエンティティのプロパティとして利用可能です。エンティティセレクションと組み合わせて使用した場合、スカラー属性はスカラー値のコレクションを返します。例:
locals:=ds .Person.query ("city = :1";"San Jose")
localEmails:=locals.emailAddress
このコードはlocalEmails 内に文字列としてのメールアドレスのコレクションを返します。
様々なクエリの方法に加えて、リレーション属性をエンティティセレクションのプロパティとして使用することで新しいエンティティセレクションを得ることもできます。例えば、以下のようなストラクチャーがある場合を考えます:
myParts:=ds .Part.query ("ID < 100")
$myInvoices :=myParts.invoiceItems.invoice
最後の行は、myParts エンティティセレクション内のパーツにリレートしている商品が少なくとも1つは含まれている全ての請求書のエンティティセレクションを、$myInvoices 内に返します。リレーション属性がエンティティセレクションのプロパティとして使用されると、返される結果は、たとえ返されるエンティティが一つだけだとしても、常に新しいエンティティセレクションとなります。リレーション属性がエンティティセレクションのプロパティとして使用されてエンティティが何も返ってこない場合、返されるのは空のエンティティセレクションであり、null ではありません。
エンティティセレクションには共有可能 (複数のプロセスから読み出し可能、ただし作成後編集不可)と追加可能 (entitySelection.add( ) ファンクションをサポートする、ただしカレントプロセスでのみ使用可能)の2種類があります:
共有可能 なエンティティセレクションは以下のような特徴を持ちます: 共有オブジェクトまたは共有コレクションに保存することが可能で、複数のプロセス間あるいはワーカー間で共有することができます。 複数の共有オブジェクトまたは共有コレクションに保存することが可能で、あるいはグループに既に属している共有オブジェクトまたは共有コレクションに保存することも可能です(言い換えるとロック識別子 を持っていないということです)。 新たにエンティティを追加することはできません。共有可能なエンティティセレクションに対してエンティティを追加しようとした場合、エラーがトリガーされます(エラー1637 - このエンティティセレクションは編集不可です)。共有可能なエンティティセレクションに対してエンティティを追加したい場合、entitySelection.add( ) を呼び出す前にまず一度をentitySelection.copy( ) 使用して共有不可のエンティティセレクションへと変換する必要があります。 注: 大部分のエンティティセレクションファンクション(entitySelection.slice( ) 、entitySelection.and( ) ...など)は共有可能エンティティセレクションをサポートしています。これらのファンクションはオリジナルのエンティティセレクションを編集しない(新しいものを返す)からです。
追加可能 なエンティティセレクションは以下のような特徴を持ちます: プロセス間での共有はできません。また共有オブジェクト/コレクションへの保存もできません。共有不可のエンティティセレクションを共有オブジェクト/コレクションに保存しようとした場合、エラーがトリガーされます(エラー -10721 - 共有オブジェクトまたはコレクションにおいてサポートされる値の型ではありません)。 新たにエンティティを追加することができます。言い換えると、entitySelection.add( ) ファンクションをサポートするということです。 エンティティセレクションが共有可能 であるか追加可能 であるかは、作成時に決定されます(後から変更することはできません)。entitySelection.isAlterable( ) ファンクションまたはOB Is shared コマンドを使用することでエンティティセレクションの性質を知ることができます。
以下の場合には、新規のエンティティセレクションは共有可能 になります:
例:
$myComp :=ds .Company.get (2)
$employees :=$myComp .employees
以下の場合には、新規のエンティティセレクションは追加可能 になります:
例:
$toModify :=ds .Company.all ().copy ()
以下の場合には、新規のエンティティセレクションはオリジナルエンティティセレクションの性質を継承 します:
例:
$highSal :=ds .Employee.query ("salary >= :1";1000000)&NBSP ;
$comp :=$highSal .employer
$lowSal :=ds .Employee.query ("salary <= :1";10000).copy ()
$comp2 :=$lowSal .employer
互換性に関する注意: dataStore.makeSelectionsAlterable( ) メソッドを使用することで、プロジェクト内のすべての新規のエンティティセレクションを"強制的に"追加可能に設定することが可能です。この互換性設定は新規のプロジェクトに対しては推奨されていません。
二つのエンティティセレクションを使用し、それらをワーカープロセスに渡して適切な相手をメールを送信したい場合を考えます:
var $paid ;$unpaid : cs .InvoicesSelection</p><p>
$paid :=ds .Invoices.query ("status=:1";"Paid")
$unpaid :=ds .Invoices.query ("status=:1";"Unpaid")
CALL WORKER ("mailing";"sendMails";$paid ;$unpaid )
sendMails メソッド:
#DECLARE ($paid cs .InvoicesSelection;$unpaid cs .InvoicesSelection)
var $invoice : cs .InvoicesEntity
var $server ;$transporter ;$email ;$status : Object
$server :=New object
$server .host:="exchange.company.com"
$server .user:="myName@company.com"
$server .password:="my!!password"
$transporter :=SMTP New transporter ($server )
$email :=New object
$email .from:="myName@company.com"
For each ($invoice ;$paid )
$email .to:=$invoice .customer.address
$email .subject:="Payment OK for invoice # "+String ($invoice .number )
$status :=$transporter .send ($email )
End for each
For each ($invoice ;$unpaid )
$email .to:=$invoice .customer.address
$email .subject:="Please pay invoice # "+String ($invoice .number )
$status :=$transporter .send ($email )
End for each
ローカルなエンティティセレクションは順列あり あるいは順列なし のどちらかです。基本的にはどちらのオブジェクトも似たような機能を持ちますが、そのパフォーマンスと利用できる機能に少し違いがあります。必要に応じて、どちらのタイプのエンティティセレクションを使用したいかを選択して下さい。
注: 次のエンティティセレクションは常に順列あり となります。
4D Server からリモートのクライアントに返されるエンティティセレクション リモートデータストアにおいて作成されるエンティティセレクション
順列なしのエンティティセレクションはメモリ内のビットテーブルに基づいてビルドされています。順列なしのエンティティセレクションは、エンティティが実際にセレクション内にあるかどうかにかかわらず、データクラス内の各エンティティに対して1ビットを格納しています。各ビットは1か0に等しく、これはエンティティがセレクションに含まれているかを表します。いわば各エンティティのとてもコンパクトな表現と言えます。 結果として、順列なしのエンティティセレクションを使用したオペレーションはとても速いです。また、順列なしのエンティティセレクションはメモリ空間という面でも経済的です。順列なしのエンティティセレクションのバイト単位のサイズは、データクラス内のエンティティの数を8で割ったものと必ず等しくなります。例えば、10,000 エンティティを格納しているデータクラスの順列なしのエンティティセレクションを作成した場合、サイズは1,250 バイトとなり、RAM内では1.2KB程度となります。 その一方で、順列なしのエンティティセレクションを並べ替えることはできません。セレクション内のエンティティの位置を当てにすることはできないということです。また、セレクション内で同じエンティティに対して二つ以上の参照を持つことはできません。言い換えると、各エンティティは一度しか追加できないということです。 順列ありのエンティティセレクションはメモリ内で(エンティティ参照を格納する)倍長整数配列に基づいてビルドされています。エンティティへの各参照はメモリ内で4バイトのサイズをとります。このようなセレクションを処理し維持することは、順列なしのセレクションより長い時間と大きいメモリ空間を必要とすることになります。 その一方で、並べ替えをしたり再並び替えをすることが可能であり、エンティティの位置に依存することができます。また、同じエンティティに対して複数の参照を持つことも可能です。 以下の表は、それぞれのエンティティセレクションのタイプの主な特徴をまとめたものです:
機能 順列なしのエンティティセレクション 順列ありエンティティセレクション 処理スピード とても速い 遅い メモリ内のサイズ とても小さい 大きい エンティティに対して複数の参照を格納する 不可 可能
最適化の観点から、4D ORDA ではデフォルトでは通常順列なしのエンティティセレクションを作成しますが、orderBy( ) メソッドを使用した場合あるいは適切なオプション(以下参照)を使用した場合は除きます。このドキュメントでは、指定されている場合を除き、"エンティティセレクション"は"順列なしのエンティティセレクション"を指すこととします。
上記で書かれているように、デフォルトでは、ORDA はクエリやand( ) などの比較のオペレーションの結果として、順列なしのエンティティセレクションを作成・管理します。 順列ありのエンティティセレクションは、必要な場合において、あるいはオプションを使用して特別に要求した場合に限り作成されます。
順列ありのエンティティセレクション は以下のような場合に作成されます:
セレクション(タイプを問わず)に対してorderBy( ) をした、あるいはデータクラスに対してorderBy( ) をした戻り値 newSelection( ) メソッドにdk keep ordered オプションを渡した戻り値 順列なしのエンティティセレクション は以下のような場合に作成されます:
セレクション(タイプを問わず)に対して標準のquery( ) をした、あるいはデータクラスに対してquery( ) をした戻り値 オプションを使用しないでnewSelection( ) メソッドを使用した戻り値 任意の比較メソッドの結果。入力セレクションタイプは問いません: or( ) , and( ) , minus( ) 順列ありのエンティティセレクションが順列なしのエンティティセレクションになった場合、重複したエンティティ参照は全て削除されます。
順列ありのエンティティセレクションを順列なしのものに変換したい場合、そのエンティティセレクションに対してand( ) オペレーションを適用するだけです。例:
mySel :=mySel .and (mySel )
4D では、クライアント/サーバー環境においてエンティティセレクションを使用する、あるいはエンティティを読み込むORDAのリクエストを自動的に最適化する機構を提供しています。この最適化機構は、ネットワーク間でやり取りされるデータの量を大幅に縮小させることで4D の実行速度を向上させます。
この機能には、以下の最適化機構が実装されています:
クライアントがサーバーに対してエンティティセレクションのリクエストを送ると、4D はコード実行の途中で、エンティティセレクションのどの属性がクライアント側で実際に使用されているかを自動的に"学習"し、それに対応した"最適化コンテキスト"をビルドします。このコンテキストはエンティティセレクションに付随し、使用された属性を保存していきます。他の属性があとで使用された場合には自動的に情報を更新していきます。 サーバー上の同じエンティティセレクションに対してその後に送られたリクエストは、最適化コンテキストを再利用して、サーバーから必要な属性のみを取得していくことで、処理を早くします。例えば、エンティティセレクション型のリストボックスにおいては、"学習"フェーズは最初の行を表示中に行われるので、次の行の表示から最適化が行われます。 既存の最適化コンテキストは、同じデータクラスの他のエンティティセレクションであればプロパティとして渡すことができるので、学習フェーズを省略して、アプリケーションをより早く実行することができます(以下の"コンテキストプロパティの使用"を参照してください)。 以下のメソッドは、返されるエンティティセレクションには、ソースのエンティティセレクションの最適化コンテキストを自動的に付随させます:
例題
以下のようなコードがあるとき:
$sel :=$ds .Employee.query ("firstname = ab@")
For each ($e ;$sel )
$s :=$e .firstname+" "+$e .lastname+" works for "+$e .employer.name
End for each
最適化機構のおかげで、このリクエストは学習フェーズ以降は、$sel の中で実際に使用されている属性(firstname, lastname, employer, employer.name)からのみデータを取得するようになります。
contextプロパティの使用
context プロパティを使用することで、最適化の利点をさらに増幅させることができます。このプロパティはあるエンティティセレクションで"学習した"最適化コンテキストを参照します。これを新しいエンティティセレクションを返すORDA メソッドに引数として渡すことで、その返されたエンティティセレクションでは学習フェーズを最初から省略してサーバーに使用された属性をリクエストすることができるようになります。
同じ最適化context プロパティは、同じデータクラスのエンティティセレクションに対してであればどのエンティティセレクションにも渡すことができます。エンティティセレクションを扱う全てのORDA メソッドは、context プロパティをサポートします(例えばdataClass.query( ) あるいは dataClass.all( ) メソッドなど)。ただし、 コンテキストはコードの他の部分で新しい属性が使用された際には自動的に更新されるという点に注意してください。同じコンテキストを異なるコードで再利用しすぎると、コンテキストを読み込み過ぎて、結果として効率が落ちる可能性があります。
注: 同様の機構は読み込まれたエンティティにも実装されており、それによって使用した属性のみがリクエストされるようになります(dataClass.get( ) メソッド参照)。
dataClass.query( ) メソッドを使用した例:
C_OBJECT ($sel1 ;$sel2 ;$sel3 ;$sel4 ;$querysettings ;$querysettings2 )
C_COLLECTION ($data )
$querysettings :=New object ("context";"shortList")
$querysettings2 :=New object ("context";"longList")
$sel1 :=ds .Employee.query ("lastname = S@";$querysettings )
$data :=extractData ($sel1 )
$sel2 :=ds .Employee.query ("lastname = Sm@";$querysettings )
$data :=extractData ($sel2 )
$sel3 :=ds .Employee.query ("lastname = Smith";$querysettings2 )
$data :=extractDetailedData ($sel3 )
$sel4 :=ds .Employee.query ("lastname = Brown";$querysettings2 )
$data :=extractDetailedData ($sel4 )
エンティティセレクション型リストボックス
クライアント/サーバー環境におけるエンティティセレクション型リストボックスにおいては、そのコンテンツを表示またはスクロールする際に、最適化が自動的に適用されます。つまり、リストボックスに表示されている属性のみがサーバーにリクエストされます。 また、リストボックスのカレント項目 プロパティ式 (コレクション/エンティティセレクション型リストボックス 参照) を介してカレントエンティティをロードする場合には、新たな"ページモード" コンテキストが提供されます。これによって、"ページ" による追加の属性リクエストが発生しても、リストボックスの初期コンテキストのオーバーロードが避けられます。なお、ページコンテキストの生成/使用はカレント項目 式を使用した場合に限ります (例えば、entitySelection[index] を介してアクセスした場合は、エンティティセレクションコンテキストが変化します)。 その後、エンティティを走査するメソッドがサーバーに送信するリクエストにも、同じ最適化が適用されます。次のメソッドは、ソースエンティティの最適化コンテキストを、戻り値のエンティティに自動的に適用します:
たとえば、次のコードは選択したエンティティをロードし、所属のエンティティセレクションを走査します。エンティティは独自のコンテキストにロードされ、リストボックスの初期コンテキストは影響されません:
$myEntity :=Form .currentElement
$myEntity :=$myEntity .next ()
互換性に関する注意: Open datastore を通して確立された接続で扱うことのできるコンテキストは、4Dのメインバージョンが同じもの同士でしか使用することができません。例えば、4D v19.x リモートアプリケーションは、4D Server v19.x データストアのコンテキストしか使うことができません。
エンティティセレクションオブジェクト自身は、オブジェクトとしてコピーすることはできません:
$myentitysel :=OB Copy (ds .Employee.all ())
しかしながら、エンティティセレクションのプロパティは取得可能です:
ARRAY TEXT ($prop ;0)
OB GET PROPERTY NAMES (ds .Employee.all ();$prop )