メインコンテンツまでスキップ
バージョン: 20 R6 BETA

データモデルクラス

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.DataStorecs.DataStoreds コマンド
cs.DataClassNamecs.EmployeedataStore.DataClassName, dataStore["DataClassName"]
cs.DataClassNameEntitycs.EmployeeEntitydataClass.get(), dataClass.new(), entitySelection.first(), entitySelection.last(), entity.previous(), entity.next(), entity.first(), entity.last(), entity.clone()
cs.DataClassNameSelectioncs.EmployeeSelectiondataClass.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 データモデルユーザークラスのオブジェクトインスタンスは、それらの親クラスのプロパティや関数を使うことができます:

クラスの説明

履歴
リリース内容
19 R4Entity クラスのエイリアス属性
19 R3Entity クラスの計算属性
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()

info

計算属性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()
info

エンティティセレクションを制限する フィルターは DataClass クラス 内で定義されます。

Entity クラス

ORDA で公開されるテーブル毎に、Entity クラスが cs クラスストアに公開されます。

  • 親クラス: 4D.Entity
  • クラス名: DataClassNameEntity (DataClassName はテーブル名です)
  • : cs.CityEntity

計算属性

Entity クラスでは、専用のキーワードを使用して 計算属性 を定義することができます:

  • Function get attributeName
  • Function set attributeName
  • Function query attributeName
  • Function 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 のデータクラス属性はこれを利用し、さらに queryorderBy の 2つの関数で機能を拡張しています。

計算属性には最低限、その値がどのように算出されるかを記述した get 関数が必要です。 属性にゲッター関数が定義されている場合、4D は対応するストレージスペースをデータストアに作成せず、代わりに属性がアクセスされるたびに関数のコードを実行します。 属性がアクセスされなければ、コードも実行されません。

計算属性は、その属性に値が割り当てられたときに実行される set 関数を実装することもできます。 セッター関数は、割り当てられた値をどのように処理するかを記述します。通常は、1つ以上のストレージ属性や、場合によっては他のエンティティにリダイレクトします。

ストレージ属性と同様に、計算属性も クエリ に含めることができます。 デフォルトでは、ORDA のクエリで計算属性が使用された場合、その属性はエンティティ毎に一度計算されます。 場合によっては、これで十分です。 しかし、特にクライアント/サーバーにおいてはパフォーマンスを向上させるため、実際のデータクラス属性に基づいた query 関数を計算属性に実装することで、それらのインデックスの恩恵を受けることができます。

同様に、計算属性を 並べ替え に含めることもできます。 デフォルトでは、ORDA の並べ替えで計算属性が使用された場合、その属性はエンティティ毎に一度計算されます。 クエリと同様に、実際のデータクラス属性に基づいた orderBy 関数を計算属性に実装することで、パフォーマンスを向上させることができます。

計算属性の定義

計算属性を作成するには、データクラスの Entity クラスget アクセサー関数を定義します。 計算属性は、データクラス属性およびエンティティ属性として自動的に利用可能になります。

その他の計算属性の関数 (setqueryorderBy) も、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 パラメーターは、以下のプロパティが含みます:

プロパティ説明
attributeNameText計算属性の名称
dataClassNameTextデータクラスの名称
kindText"get"
戻り値バリアント任意。 スカラー属性が 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 パラメーターは、以下のプロパティが含みます:

プロパティ説明
attributeNameText計算属性の名称
dataClassNameTextデータクラスの名称
kindText"set"
valueバリアント計算属性によって処理されるべき値

例題

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 パラメーターは、以下のプロパティが含みます:

プロパティ説明
attributeNameText計算属性の名称
dataClassNameTextデータクラスの名称
kindText"query"
valueバリアント計算属性によって処理されるべき値
operatorTextクエリ演算子 (query クラス関数も参照ください)。 とりうる値:
  • == (と等しい; @ はワイルドカード)
  • === (と等しい; @ はワイルドカードでない)
  • != (と等しくない; @ はワイルドカード)
  • !== (と等しくない; @ はワイルドカードでない)
  • < (小さい)
  • <= (less than or equal to)
  • > (大きい)
  • >= (以上)
  • IN (含まれる)
  • % (キーワードを含む)
  • 戻り値バリアント計算属性によって処理されるべき値。 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 パラメーターは、以下のプロパティが含みます:

    プロパティ説明
    attributeNameText計算属性の名称
    dataClassNameTextデータクラスの名称
    kindText"orderBy"
    valueバリアント計算属性によって処理されるべき値
    operatorText"desc" または "asc" (デフォルト)
    descendingBoolean降順の場合は true, 昇順の場合は false
    戻り値バリアント計算属性によって処理されるべき値。 4D にデフォルトソートを実行させるには、Null を渡します。

    operatordescending プロパティのどちらを使っても構いません。 これは、基本的にプログラミングのスタイルの問題です (例題参照)。

    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" の場合、エイリアスの type4D.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 クラス変数の例です: