データモデルクラス
ORDA を使用して、データモデル上に高レベルクラス関数を作成することができます。 これによってビジネス指向のコードを書き、APIのように "公開" することができます。 データストア、データクラス、エンティティ、およびエンティティセレクションはそれぞれ、関数を持つことのできるクラスオブジェクトとして提供されています。
たとえば、選択中の社員より給与の高い社員一覧を返す getNextWithHigherSalary()
関数を EmployeeEntity
クラスに作成したとします。 この関数は簡単に呼び出すことができます:
$nextHigh:=ds.Employee.get(1).getNextWithHigherSalary()
これらの関数はローカルデータストアだけでなく、クライアント/サーバーやリモートアーキテクチャーでも使用することができます:
//$cityManager はリモートデータストアへの参照です
Form.comp.city:=$cityManager.City.getCityName(Form.comp.zipcode)
この機能により、4D アプルケーションのビジネスロジックをまるごと独立したレイヤーに保存し、高レベルのセキュリティで簡単に管理・利用することができます:
-
わかりやすく使いやすい関数のみを公開し、その裏にある構造の複雑性を "隠す" ことができます。
-
構造が発展した場合には影響を受ける関数を適応させるだけで、クライアントアプリケーションは引き続き透過的にそれらを呼び出すことができます。
-
デフォルトでは、データモデルクラス関数 (計算属性関数 含む) および エイリアス属性 はすべて、リモートアプリケーションに対して 非公開 に設定されており、RESTリクエストで呼び出すことはできません。 公開する関数やエイリアスは
exposed
キーワードによって明示的に宣言する必要があります。
各データモデルオブジェクトに関わるクラスは、4D によって あらかじめ自動的に作成 されます。
アーキテクチャー
ORDA では、4D
クラスストア を介して公開される 汎用クラス と、cs
クラスストア で公開される ユーザークラス が提供されています:
ORDA データモデルクラスはすべて cs
クラスストアのプロパティとして公開されます。 次の ORDA クラスが提供されています:
クラス | 例 | 次によってインスタンス化されます |
---|---|---|
cs.DataStore | cs.DataStore | ds コマンド |
cs.DataClassName | cs.Employee | dataStore.DataClassName , dataStore["DataClassName"] |
cs.DataClassNameEntity | cs.EmployeeEntity | dataClass.get() , dataClass.new() , entitySelection.first() , entitySelection.last() , entity.previous() , entity.next() , entity.first() , entity.last() , entity.clone() |
cs.DataClassNameSelection | cs.EmployeeSelection | dataClass.query() , entitySelection.query() , dataClass.all() , dataClass.fromCollection() , dataClass.newSelection() , entitySelection.drop() , entity.getSelection() , entitySelection.and() , entitySelection.minus() , entitySelection.or() , entitySelection.orderBy() , entitySelection.orderByFormula() , entitySelection.slice() , Create entity selection |
ORDA ユーザークラスは通常のクラスファイル (.4dm) としてプロジェクトの Classes サブフォルダーに保存されます (後述参照)。
ORDA データモデルユーザークラスのオブジェクトインスタンスは、それらの親クラスのプロパティや関数を使うことができます:
- Datastore クラスオブジェクトは、ORDA Datastore 汎用クラス の関数を呼び出すことができます。
- DataClass クラスオブジェクトは、ORDA DataClass 汎用クラス の関数を呼び出すことができます。
- EntitySelection クラスオブジェクトは ORDA EntitySelection 汎用クラス の関数を呼び出すことができます。
- Entity クラスオブジェクトは ORDA Entity 汎用クラス の関数を呼び出すことができます。
クラスの説明
履歴
リリース | 内容 |
---|---|
19 R4 | Entity クラスのエイリアス属性 |
19 R3 | Entity クラスの計算属性 |
18 R5 | データモデルクラス関数は、デフォルトでは REST に公開されません。 新しい exposed および local キーワード。 |
DataStore クラス
4D のデータベースは、自身の DataStore クラスを cs
クラスストアに公開します。
- 親クラス: 4D.DataStoreImplementation
- クラス名: cs.DataStore
DataStore クラス内には、ds
オブジェクトを介して使用する関数を作成することができます。
例題
// cs.DataStore class
Class extends DataStoreImplementation
Function getDesc
$0:="社員と会社を公開するデータベース"
この関数は次のように使えます:
$desc:=ds.getDesc() //"社員と会社を..."
DataClass クラス
ORDA で公開されるテーブル毎に、DataClass クラスが cs
クラスストアに公開されます。
- 親クラス: 4D.DataClass
- クラス名: cs.DataClassName (DataClassName はテーブル名です)
- 例: cs.Employee
例題
// cs.Company クラス
Class extends DataClass
// 収益が平均以上の会社を返します
// Company DataClass にリレートしているエンティティセレクションを返します
Function GetBestOnes()
$sel:=This.query("revenues >= :1";This.all().average("revenues"));
$0:=$sel
全会社データから平均以上の会社データをエンティティセレクションに抽出するには次を実行します:
var $best : cs.CompanySelection
$best:=ds.Company.GetBestOnes()
計算属性 は Entity クラス において定義されます。
リモートデータストアの例
次の City カタログをリモートデータストアとして公開しています:
City クラス
は API を提供しています:
// cs.City クラス
Class extends DataClass
Function getCityName()
var $1; $zipcode : Integer
var $zip : 4D.Entity
var $0 : Text
$zipcode:=$1
$zip:=ds.ZipCode.get($zipcode)
$0:=""
If ($zip#Null)
$0:=$zip.city.name
End if
クライアントはまず、リモートデータストアのセッションを開始します:
$cityManager:=Open datastore(New object("hostname";"127.0.0.1:8111");"CityManager")
クライアントアプリケーションは API を使い、たとえばフォームに入力された郵便番号 (zipcode) に合致する都市を取得することができます:
Form.comp.city:=$cityManager.City.getCityName(Form.comp.zipcode)
EntitySelection クラス
ORDA で公開されるテーブル毎に、EntitySelection クラスが cs
クラスストアに公開されます。
- 親クラス: 4D.EntitySelection
- クラス名: DataClassNameSelection (DataClassName はテーブル名です)
- 例: cs.EmployeeSelection
例題
// cs.EmployeeSelection クラス
Class extends EntitySelection
// 給与が平均超えの社員を当該エンティティセレクションから抽出します
Function withSalaryGreaterThanAverage() : cs.EmployeeSelection
return This.query("salary > :1";This.average("salary")).orderBy("salary")
任意の社員エンティティセレクションより、給与が平均以上の社員を取得するには:
$moreThanAvg:=ds.Company.all().employees.withSalaryGreaterThanAverage()
エンティティセレクションを制限する フィルターは DataClass クラス 内で定義されます。
Entity クラス
ORDA で公開されるテーブル毎に、Entity クラスが cs
クラスストアに公開されます。
- 親クラス: 4D.Entity
- クラス名: DataClassNameEntity (DataClassName はテーブル名です)
- 例: cs.CityEntity
計算属性
Entity クラスでは、専用のキーワードを使用して 計算属性 を定義することができます:
Function get
attributeNameFunction set
attributeNameFunction query
attributeNameFunction orderBy
attributeName
詳細については、計算属性 を参照してください。
エイリアス属性
Entity クラスでは、Alias
キーワードを使用して エイリアス属性 を定義することができます (通常はリレート属性を対象に定義します):
Alias
attributeName targetPath
詳細については、エイリアス属性 を参照してください。
例題
// cs.CityEntity クラス
Class extends Entity
Function getPopulation() : Integer
return This.zips.sum("population")
Function isBigCity(): Boolean
// 関数 getPopulation() をクラス内で使用することができます
return This.getPopulation()>50000
次のように関数を呼び出すことができます:
var $cityManager; $city : Object
$cityManager:=Open datastore(New object("hostname";"127.0.0.1:8111");"CityManager")
$city:=$cityManager.City.getCity("Caguas")
If ($city.isBigCity())
ALERT($city.name + " は大きな町です。")
End if
定義規則
データモデルクラスを作成・編集する際には次のルールに留意しなくてはなりません:
-
4D のテーブル名は、cs クラスストア 内において自動的に DataClass クラス名として使用されるため、cs 名前空間において衝突があってはなりません。 特に:
- 4D テーブルと ユーザークラス名 に同じ名前を使用してはいけません。 衝突が起きた場合には、ユーザークラスのコンストラクターは使用不可となります (コンパイラーにより警告が返されます)。
- 4D テーブルに予約語を使用してはいけません (例: "DataClass")。
-
クラス定義の際、
Class extends
ステートメントに使用する親クラスの名前は完全に合致するものでなくてはいけません (文字の大小が区別されます)。 たとえば、EntitySelection クラスを継承するにはClass extends EntitySelection
と書きます。 -
データモデルクラスオブジェクトのインスタンス化に
new()
キーワードは使えません (エラーが返されます)。 上述の ORDA クラステーブルに一覧化されている、通常の インスタンス化の方法 を使う必要があります。 -
4D
クラスストア のネイティブな ORDA クラス関数を、データモデルユーザークラス関数でオーバーライドすることはできません。
プリエンプティブ実行
コンパイル済みの状態では、データモデルクラス関数は次のように実行されます:
- シングルユーザーアプリケーションでは、プリエンプティブまたはコオペラティブプロセス で実行されます (呼び出し元のプロセスに依存します)。
- クライアント/サーバーアプリケーションでは、プリエンプティブプロセス で実行されます (ただし、
local
キーワードが使用されている場合は、シングルユーザーの場合と同様に、呼び出し元プロセスに依存します)。
クライアント/サーバーで動作するように設計されているプロジェクトでは、データモデルクラス関数のコードがスレッドセーフであることを確認してください。 スレッドセーフでないコードが呼び出された場合、実行時にエラーが発生します (シングルユーザーアプリケーションではコオペラティブ実行がサポートされているため、コンパイル時にはエラーが発生しません)。
計算属性
概要
計算属性は、計算をマスクするデータ型を持つデータクラス属性です。 標準的な 4Dクラスは、get
(ゲッター) および set
(セッター) アクセサー関数 を用いて、計算プロパティの概念を実装しています。 ORDA のデータクラス属性はこれを利用し、さらに query
と orderBy
の 2つの関数で機能を拡張しています。
計算属性には最低限、その値がどのように算出されるかを記述した get
関数が必要です。 属性にゲッター関数が定義されている場合、4D は対応するストレージスペースをデータストアに作成せず、代わりに属性がアクセスされるたびに関数のコードを実行します。 属性がアクセスされなければ、コードも実行されません。
計算属性は、その属性に値が割り当てられたときに実行される set
関数を実装することもできます。 セッター関数は、割り当てられた値をどのように処理するかを記述します。通常は、1つ以上のストレージ属性や、場合によっては他のエンティティにリダイレクトします。
ストレージ属性と同様に、計算属性も クエリ に含めることができます。 デフォルトでは、ORDA のクエリで計算属性が使用された場合、その属性はエンティティ毎に一度計算されます。 場合によっては、これで十分です。 しかし、特にクライアント/サーバーにおいてはパフォーマンスを向上させるため、実際のデータクラス属性に基づいた query
関数を計算属性に実装することで、それらのインデックスの恩恵を受けることができます。
同様に、計算属性を 並べ替え に含めることもできます。 デフォルトでは、ORDA の並べ替えで計算属性が使用された場合、その属性はエンティティ毎に一度計算されます。 クエリと同様に、実際のデータクラス属性に基づいた orderBy
関数を計算属性に実装することで、パフォーマンスを向上させることができます。
計算属性の定義
計算属性を作成するには、データクラスの Entity クラス に get
アクセサー関数を定義します。 計算属性は、データクラス属性およびエンティティ属性として自動的に利用可能になります。
その他の計算属性の関数 (set
、query
、orderBy
) も、Entityクラスに定義することができます。 これらの関数の定義は任意です。
計算属性の関数内において、This
はエンティティを指します。 計算属性は、他のデータクラス属性と同様に使用することができます。つまり、Entity クラス や EntitySelection クラス の関数によっても同様に処理されます。
ORDA の計算属性は、デフォルトでは 公開 されません。 計算属性を公開するには、get 関数 の定義に
exposed
キーワードを追加します。
get および set関数は、クライアント/サーバー処理を最適化するために、local プロパティを持つこともできます。
Function get <attributeName>
シンタックス
{local} {exposed} Function get <attributeName>({$event : Object}) -> $result : type
// コード
ゲッター 関数は、attributeName 計算属性を宣言するために必須です。 attributeName がアクセスされるたびに、4D は Function get
のコードを評価し、$result 値を返します。
計算属性は、他の計算属性の値を使用することができます。 再帰的な呼び出しはエラーになります。
ゲッター 関数は、$result パラメーターに基づいて、計算属性のデータ型を定義します。 以下の結果の型が可能です:
- スカラー (テキスト、ブール、日付、時間、数値)
- オブジェクト
- ピクチャー
- BLOB
- エンティティ (例: cs.EmployeeEntity)
- エンティティセレクション (例: cs.EmployeeSelection)
$event パラメーターは、以下のプロパティが含みます:
プロパティ | 型 | 説明 |
---|---|---|
attributeName | Text | 計算属性の名称 |
dataClassName | Text | データクラスの名称 |
kind | Text | "get" |
戻り値 | Variant | 任意。 スカラー属性が Null を返すようにするには、このプロパティを Null値で追加します。 |
例題
- fullName 計算属性:
Function get fullName($event : Object)-> $fullName : Text
Case of
: (This.firstName=Null) & (This.lastName=Null)
$event.result:=Null // Null値を返すには result を使用します
: (This.firstName=Null)
$fullName:=This.lastName
: (This.lastName=Null)
$fullName:=This.firstName
Else
$fullName:=This.firstName+" "+This.lastName
End case
- 計算属性は、エンティティにリレートされた属性に基づいて定義することができます。
Function get bigBoss($event : Object)-> $result: cs.EmployeeEntity
$result:=This.manager.manager
- 計算属性は、エンティティセレクションにリレートされた属性に基づいて定義することができます。
Function get coWorkers($event : Object)-> $result: cs.EmployeeSelection
If (This.manager.manager=Null)
$result:=ds.Employee.newSelection()
Else
$result:=This.manager.directReports.minus(this)
End if
Function set <attributeName>
シンタックス
{local} Function set <attributeName>($value : type {; $event : Object})
// コード
セッター 関数は、属性に値が割り当てられたときに実行されます。 この関数は通常、入力値を処理し、その結果を 1つ以上の他の属性に転送します。
$value パラメーターは、属性に割り当てられた値を受け取ります。
$event パラメーターは、以下のプロパティが含みます:
プロパティ | 型 | 説明 |
---|---|---|
attributeName | Text | 計算属性の名称 |
dataClassName | Text | データクラスの名称 |
kind | Text | "set" |
value | Variant | 計算属性によって処理されるべき値 |
例題
Function set fullName($value : Text; $event : Object)
var $p : Integer
$p:=Position(" "; $value)
This.firstname:=Substring($value; 1; $p-1) // "" if $p<0
This.lastname:=Substring($value; $p+1)
Function query <attributeName>
シンタックス
Function query <attributeName>($event : Object)
Function query <attributeName>($event : Object) -> $result : Text
Function query <attributeName>($event : Object) -> $result : Object
// コード
このメソッドは 3種類のシンタックスを受け入れます:
- 最初のシンタックスでは、
$event.result
オブジェクトプロパティを通じてクエリ全体を処理します。 - 2番目と 3番目のシンタックスでは、関数は $result に値を返します:
- $result がテキストの場合、それは有効なクエリ文字列でなければなりません。
- $result がオブジェクトの場合、次の 2つのプロパティを含まなければなりません: | プロパティ | 型 | 説明 | | ---------------------------------- | ---------- | -------------------------------------------------------------------------------------- | | $result.query | Text | プレースホルダー (:1, :2, など) を使った有効なクエリ文字列 | | $result.parameters | Collection | プレースホルダーに渡す値 |
query
関数は、計算属性を使用するクエリが開始されるたびに実行されます。 インデックス付きの属性を利用することで、クエリをカスタマイズしたり最適化したりすることができます。 計算属性に対して query
関数が実装されていない場合、検索は常にシーケンシャルにおこなわれます (get <AttributeName>
関数によるすべての値の評価に基づきます)。
以下の機能はサポートされていません:
- エンティティ、またはエンティティセレクション型の計算属性に対する
query
関数の呼び出し- 結果のクエリ文字列における
order by
キーワードの使用
$event パラメーターは、以下のプロパティが含みます:
プロパティ | 型 | 説明 |
---|---|---|
attributeName | Text | 計算属性の名称 |
dataClassName | Text | データクラスの名称 |
kind | Text | "query" |
value | Variant | 計算属性によって処理されるべき値 |
operator | Text | クエリ演算子 (query クラス関数も参照ください)。 とりうる値: |
戻り値 | Variant | 計算属性によって処理されるべき値。 4D がデフォルトクエリ (計算属性では常にシーケンシャル) を実行するようにしたい場合は、このプロパティに Null を渡します。 |
関数が $result に値を返し、
$event.result
プロパティにも別の値が割り当てられている場合、$event.result
が優先されます。
例題
- fullName 計算属性のクエリ:
Function query fullName($event : Object)->$result : Object
var $fullname; $firstname; $lastname; $query : Text
var $operator : Text
var $p : Integer
var $parameters : Collection
$operator:=$event.operator
$fullname:=$event.value
$p:=Position(" "; $fullname)
If ($p>0)
$firstname:=Substring($fullname; 1; $p-1)+"@"
$lastname:=Substring($fullname; $p+1)+"@"
$parameters:=New collection($firstname; $lastname) // 2要素のコレクション
Else
$fullname:=$fullname+"@"
$parameters:=New collection($fullname) // 1要素のコレクション
End if
Case of
: ($operator="==") | ($operator="===")
If ($p>0)
$query:="(firstName = :1 and lastName = :2) or (firstName = :2 and lastName = :1)"
Else
$query:="firstName = :1 or lastName = :1"
End if
: ($operator="!=")
If ($p>0)
$query:="firstName != :1 and lastName != :2 and firstName != :2 and lastName != :1"
Else
$query:="firstName != :1 and lastName != :1"
End if
End case
$result:=New object("query"; $query; "parameters"; $parameters)
ユーザーのテキスト入力に基づくクエリでは、セキュリティ上の理由からプレースホルダーを使用することが推奨されています (
query()
の説明 参照)。
呼び出しコードの例:
$emps:=ds.Employee.query("fullName = :1"; "Flora Pionsin")
- この関数は age (年齢) 計算属性に対するクエリを処理し、パラメーターを含むオブジェクトを返します:
Function query age($event : Object)->$result : Object
var $operator : Text
var $age : Integer
var $_ages : Collection
$operator:=$event.operator
$age:=Num($event.value) // 整数
$d1:=Add to date(Current date; -$age-1; 0; 0)
$d2:=Add to date($d1; 1; 0; 0)
$parameters:=New collection($d1; $d2)
Case of
: ($operator="==")
$query:="birthday > :1 and birthday <= :2" // d1 より大きい、かつ d2 以下
: ($operator="===")
$query:="birthday = :2" // d2 = 2つ目の算出値 (= 誕生日)
: ($operator=">=")
$query:="birthday <= :2"
//... その他の演算子
End case
If (Undefined($event.result))
$result:=New object
$result.query:=$query
$result.parameters:=$parameters
End if
呼び出しコードの例:
// 20歳以上で 21歳未満の人
$twenty:=people.query("age = 20") // "==" のケースを呼び出します
// 本日満 20歳になった人
$twentyToday:=people.query("age === 20") // people.query("age is 20") と同じ
Function orderBy <attributeName>
シンタックス
Function orderBy <attributeName>($event : Object)
Function orderBy <attributeName>($event : Object)-> $result : Text
// コード
orderBy
関数は、計算属性で並べ替えされるたびに実行されます。 これにより、計算属性で並べ替えることができます。 たとえば、fullName を名字、名前の順にソートしたり、逆に名字、名前の順にソートすることができます。
計算属性に対して orderBy
関数が実装されていない場合、並べ替えは常にシーケンシャルにおこなわれます (get <AttributeName>
関数によるすべての値の評価に基づきます)。
Entity クラス、または EntitySelection クラス型の計算属性に対する
orderBy
関数の呼び出しは サポートされていません。
$event パラメーターは、以下のプロパティが含みます:
プロパティ | 型 | 説明 |
---|---|---|
attributeName | Text | 計算属性の名称 |
dataClassName | Text | データクラスの名称 |
kind | Text | "orderBy" |
value | Variant | 計算属性によって処理されるべき値 |
operator | Text | "desc" または "asc" (デフォルト) |
descending | Boolean | 降順の場合は true , 昇順の場合は false |
戻り値 | Variant | 計算属性によって処理されるべき値。 4D にデフォルトソートを実行させるには、Null を渡します。 |
operator
とdescending
プロパティのどちらを使っても構いません。 これは、基本的にプログラミングのスタイルの問題です (例題参照)。
orderBy
文字列は、$event.result
オブジェクトプロパティまたは関数の戻り値である $result のどちらでにも返すことができます。 関数が $result に値を返し、$event.result
プロパティにも別の値が割り当てられている場合、$event.result
が優先されます。
例題
次のような条件分岐のコードを書くことができます:
Function orderBy fullName($event : Object)-> $result : Text
If ($event.descending=True)
$result:="firstName desc, lastName desc"
Else
$result:="firstName, lastName"
End if
また、次のような短縮コードを書くこともできます:
Function orderBy fullName($event : Object)-> $result : Text
$result:="firstName "+$event.operator+", "lastName "+$event.operator
場合によっては条件分岐のコードが必要です:
Function orderBy age($event : Object)-> $result : Text
If ($event.descending=True)
$result:="birthday asc"
Else
$result:="birthday desc"
End if
エイリアス属性
概要
エイリアス 属性は、ターゲット 属性と呼ばれるデータモデルの別の属性を元に定義されます。 ターゲット属性には、リレートデータクラス (リレートレベルは無制限) または同じデータクラスのものを使用できます。 エイリアス属性はデータではなく、ターゲット属性へのパスを格納します。 データクラスには、必要な数だけエイリアス属性を定義することができます。
エイリアス属性は、N対Nリレーションを扱うのに便利です。 実装の詳細ではなくビジネスの概念を扱ってコードやクエリを作成できるため、これらの可読性が向上します。
エイリアス属性の定義
データクラス内にエイリアス属性を作成するには、データクラスの Entityクラス において Alias
キーワードを使用します。
Alias <attributeName> <targetPath>
シンタックス
{exposed} Alias <attributeName> <targetPath>
attributeName は、プロパティ名の命名規則 に準拠している必要があります。
targetPath は、"employee.company.name" のような、1つ以上のレベルを含む属性パスです。 ターゲット属性が同じデータクラスに属している場合、targetPath は属性名となります。
エイリアスは、他のエイリアスのパスに使用することができます。
計算属性 もエイリアスパスに使用することができますが、パスの最後のレベルとしてのみ使用できます。そうでない場合は、エラーが返されます。 たとえば、"fullName" 計算属性がある場合、"employee.fullName" というエイリアスパスは有効です。
ORDA のエイリアス属性は、デフォルトでは 公開されません。 リモートリクエストでエイリアスを利用するには、
Alias
キーワードの前にexposed
キーワードを追加する必要があります。
エイリアス属性の使用
エイリアス属性は読み取り専用です (同じデータクラスのスカラー属性に基づく場合は例外です; 最後の例題参照)。 エイリアス属性は、次のようなクラス関数において、ターゲット属性パスの代わりに使用することができます:
Function |
---|
dataClass.query() , entitySelection.query() |
entity.toObject() |
entitySelection.toCollection() |
entitySelection.extract() |
entitySelection.orderBy() |
entitySelection.orderByFormula() |
entitySelection.average() |
entitySelection.count() |
entitySelection.distinct() |
entitySelection.sum() |
entitySelection.min() |
entitySelection.max() |
entity.diff() |
entity.touchedAttributes() |
エイリアス属性はサーバー上で計算されることに留意してください。 リモート環境において、エンティティのエイリアス属性を更新するには、エンティティをサーバーから再ロードする必要があります。
エイリアスのプロパティ
エイリアス属性の kind
プロパティ (属性の種類) は "alias" です。
エイリアス属性は、ターゲット属性の type
プロパティを継承します。
- ターゲット属性の
kind
プロパティが "storage" の場合、エイリアス属性のtype
はターゲット属性と同じになります。 - ターゲット属性の
kind
が "relatedEntity" または "relatedEntities" の場合、エイリアスのtype
は4D.Entity
または4D.EntitySelection
("classnameEntity" または "classnameSelection") になります。
リレーションに基づくエイリアス属性は、そのターゲット属性のパスを格納する専用の path
プロパティを持ちます。 同じデータクラスの属性に基づくエイリアス属性は、ターゲット属性と同じプロパティを持ちます (path
プロパティはありません)。
例題
以下のモデルがあるとき:
Teacher データクラスに、教師の生徒をすべて返すエイリアス属性を定義します:
// cs.TeacherEntity クラス
Class extends Entity
Alias students courses.student //relatedEntities
Student データクラスには、生徒の教師をすべて返すエイリアス属性を定義します:
// cs.StudentEntity クラス
Class extends Entity
Alias teachers courses.teacher //relatedEntities
Course データクラスには次を定義します:
- "name" 属性を別名で参照するためのエイリアス属性
- 教師の名前を返すエイリアス属性
- 生徒の名前を返すエイリアス属性
// cs.CourseEntity クラス
Class extends Entity
Exposed Alias courseName name //スカラー値
Exposed Alias teacherName teacher.name //スカラー値
Exposed Alias studentName student.name //スカラー値
すると、以下のクエリを実行することができます:
// "Archaeology" の授業を検索します
ds.Course.query("courseName = :1";"Archaeology")
// Smith 教師が教えている授業を検索します
ds.Course.query("teacherName = :1";"Smith")
// 生徒 "Martin" が参加している授業を検索します
ds.Course.query("studentName = :1";"Martin")
// M. Smith 教師の生徒を検索します
ds.Student.query("teachers.name = :1";"Smith")
// M. Martin を生徒に持つ教師を検索します
ds.Teacher.query("students.name = :1";"Martin")
// シンプルなクエリ文字列で複雑なクエリを実行していることに注目してください
// queryPlan は次のとおりです:
// "Join on Table : Course : Teacher.ID = Course.teacherID,
// subquery:[ Join on Table : Student : Course.studentID = Student.ID,
// subquery:[ Student.name === Martin]]"
courseName エイリアスの値は編集することができます:
// エイリアス属性を使って、授業の名称を変更します
$arch:=ds.Course.query("courseName = :1";"Archaeology")
$arch.courseName:="Archaeology II"
$arch.save() //courseName と name は "Archaeology II" に変更されます
公開vs非公開関数
セキュリティ上の理由により、データモデルクラス関数およびエイリアス属性はデフォルトですべて、リモートリクエストに対し 非公開 (つまりプライベート) に設定されています。
リモートリクエストには次のものが含まれます:
Open datastore
によって接続されたリモートの 4Dアプリケーションが送信するリクエスト- RESTリクエスト
通常の 4Dクライアント/サーバーリクエストは影響されません。 このアーキテクチャーにおいては、データモデルクラス関数は常に利用可能です。
公開されていない関数はリモートアプリケーションで利用することができず、RESTリクエストによるオブジェクトインスタンスに対して呼び出すこともできません。 リモートアプリケーションが非公開関数をアクセスしようとすると、"-10729 (未知のメンバー機能です)" エラーが返されます。
リモートリクエストによる呼び出しを許可するには、exposed
キーワードを使ってデータモデルクラス関数を明示的に宣言する必要があります。 シンタックスは次の通りです:
// 公開関数の宣言
exposed Function <functionName>
exposed
キーワードは、データモデルクラス関数に対してのみ利用可能です。 通常のユーザークラス 関数に対して使った場合、キーワードは無視され、コンパイラーはエラーを返します。
例題
公開された関数によって、DataClass クラスのプライベート関数を呼び出します:
Class extends DataClass
// 公開関数
exposed Function registerNewStudent($student : Object) -> $status : Object
var $entity : cs.StudentsEntity
$entity:=ds.Students.new()
$entity.fromObject($student)
$entity.school:=This.query("name=:1"; $student.schoolName).first()
$entity.serialNumber:=This.computeSerialNumber()
$status:=$entity.save()
// 非公開 (プライベート) 関数
Function computeIDNumber()-> $id : Integer
// 新規ID番号を算出します
$id:=...
呼び出し元のコードは次の通りです:
var $remoteDS; $student; $status : Object
var $id : Integer
$remoteDS:=Open datastore(New object("hostname"; "127.0.0.1:8044"); "students")
$student:=New object("firstname"; "Mary"; "lastname"; "Smith"; "schoolName"; "Math school")
$status:=$remoteDS.Schools.registerNewStudent($student) // OK
$id:=$remoteDS.Schools.computeIDNumber() // エラー (未知のメンバー機能です)
ローカル関数
クライアント/サーバーアーキテクチャーではデフォルトで、ORDA データモデル関数は サーバー上で 実行されます。 関数リクエストとその結果だけが通信されるため、通常はベストパフォーマンスが提供されます。
しかしながら、状況によってはその関数はクライアント側で完結するものかもしれません (たとえば、すでにローカルキャッシュにあるデータを処理する場合など)。 そのような場合には、local
キーワードを使ってサーバーへのリクエストをおこなわないようにし、アプリケーションのパフォーマンスを向上させることができます。 シンタックスは次の通りです:
// クライアント/サーバーにおいてローカル実行する関数の宣言
local Function <functionName>
このキーワードを使うと、関数は常にクライアントサイドで実行されます。
local
キーワードは、データモデルクラス関数に対してのみ利用可能です。 通常のユーザークラス 関数に対して使った場合、キーワードは無視され、コンパイラーはエラーを返します。
最終的にサーバーへのアクセスが必要になっても (ORDAキャッシュが有効期限切れになった場合など) 関数は動作します。 もっとも、それではローカル実行によるパフォーマンスの向上は見込めないため、ローカル関数がサーバー上のデータにアクセスしないことを確認しておくことが推奨されます。 サーバーに対して複数のリクエストをおこなうローカル関数は、サーバー上で実行されて結果だけを返す関数よりも非効率的です。 たとえば、Schools Entityクラスの次の関数を考えます:
// 2000年以降の生まれの生徒を検索します
// local キーワードを適切に使用していない例です
local Function getYoungest
var $0 : Object
$0:=This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5)
local
キーワードを 使わない 場合、1つのリクエストで結果が得られます。local
キーワードを 使う 場合、4つのリクエストが必要になります: Schools エンティティの students エンティティセレクションの取得、query()
の実行、orderBy()
の実行、slice()
の実行。 この例では、local
キーワードを使用するのは適切ではありません。
例題
年齢の計算
birthDate (生年月日) 属性を持つエンティティがある場合に、リストボックス内で呼び出すための age()
関数を定義します。 この関数をクライアントサイドで実行することで、リストボックスの各行がサーバーへのリクエストを生成するのを防ぎます。
StudentsEntity クラス:
Class extends Entity
local Function age() -> $age: Variant
If (This.birthDate#!00-00-00!)
$age:=Year of(Current date)-Year of(This.birthDate)
Else
$age:=Null
End if
属性のチェック
クライアントにロードされ、ユーザーによって更新されたエンティティの属性について、サーバーへ保存リクエストを出すまえに、それらの一貫性を検査します。
StudentsEntity クラスのローカル関数 checkData()
は生徒の年齢をチェックします:
Class extends Entity
local Function checkData() -> $status : Object
$status:=New object("success"; True)
Case of
: (This.age()=Null)
$status.success:=False
$status.statusText:="生年月日が入力されていません。"
:((This.age() <15) | (This.age()>30) )
$status.success:=False
$status.statusText:="生徒の年齢は 15 〜 30 の範囲で入力してください。この生徒の年齢は "+String(This.age()+"です。")
End case
呼び出し元のコード:
var $status : Object
// Form.student は全属性とともにロードされており、フォーム上で更新されました
$status:=Form.student.checkData()
If ($status.success)
$status:=Form.student.save() // サーバーを呼び出します
End if
4D IDE (統合開発環境) におけるサポート
クラスファイル
ORDA データモデルユーザークラスは、クラスと同じ名称の .4dm ファイルを 通常のクラスファイルと同じ場所 (つまり、Project フォルダー内の /Sources/Classes
フォルダー) に追加することで定義されます。 たとえば、Utilities
データクラスのエンティティクラスは、UtilitiesEntity.4dm
ファイルによって定義されます。
クラスの作成
各データモデルオブジェクトに関わるクラスは、4D によってあらかじめ自動的にメモリ内に作成されます。
空の ORDA クラスは、デフォルトではエクスプローラーに表示されません。 表示するにはエクスプローラーのオプションメニューより データクラスを全て表示 を選択します:
ORDA ユーザークラスは通常のクラスとは異なるアイコンで表されます。 空のクラスは薄く表示されます:
ORDA クラスファイルを作成するには、エクスプローラーで任意のクラスをダブルクリックします。 4D はクラスファイルを作成し、extends
ステートメントを自動で追加します。 たとえば、Entity クラスを継承するクラスの場合は:
Class extends Entity
定義されたクラスはエクスプローラー内で濃く表示されます。
クラスの編集
定義された ORDA クラスファイルを 4Dコードエディターで開くには、ORDA クラス名を選択してエクスプローラーのオブションメニュー、またはコンテキストメニューの 編集... を使用するか、ORDA クラス名をダブルクリックします:
ローカルデータストア (ds
) に基づいた ORDA クラスの場合には、4D ストラクチャーウィンドウからも直接クラスコードにアクセスできます:
コードエディター
4Dコードエディターにおいて、ORDA クラス型として定義された変数は、自動補完機能の対象となります。 Entity クラス変数の例です: