Skip to main content
Version: 19

Data Model Classes

ORDA allows you to create high-level class functions above the data model. This allows you to write business-oriented code and "publish" it just like an API. Datastore, dataclasses, entity selections, and entities are all available as class objects that can contain functions.

For example, you could create a getNextWithHigherSalary() function in the EmployeeEntity class to return employees with a salary higher than the selected one. It would be as simple as calling:

$nextHigh:=ds.Employee.get(1).getNextWithHigherSalary()

Developers can not only use these functions in local datastores, but also in client/server and remote architectures:

 //$cityManager is the reference of a remote datastore
Form.comp.city:=$cityManager.City.getCityName(Form.comp.zipcode)

Thanks to this feature, the entire business logic of your 4D application can be stored as a independent layer so that it can be easily maintained and reused with a high level of security:

  • You can "hide" the overall complexity of the underlying physical structure and only expose understandable and ready-to-use functions.

  • If the physical structure evolves, you can simply adapt function code and client applications will continue to call them transparently.

  • By default, all of your data model class functions are not exposed to remote applications and cannot be called from REST requests. You must explicitly declare each public function with the exposed keyword.

In addition, 4D automatically pre-creates the classes for each available data model object.

Architecture

ORDA provides generic classes exposed through the 4D class store, as well as user classes (extending generic classes) exposed in the cs class store:

All ORDA data model classes are exposed as properties of the cs class store. The following ORDA classes are available:

ClassExample nameInstantiated by
cs.DataStorecs.DataStoreds command
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 user classes are stored as regular class files (.4dm) in the Classes subfolder of the project (see below).

Also, object instances from ORDA data model user classes benefit from their parent's properties and functions:

Class Description

History
ReleaseChanges
18 R5Data model class functions are not exposed to REST by default. New exposed and local keywords.

DataStore Class

A 4D database exposes its own DataStore class in the cs class store.

  • Extends: 4D.DataStoreImplementation
  • Class name: cs.DataStore

You can create functions in the DataStore class that will be available through the ds object.

Example

// cs.DataStore class

Class extends DataStoreImplementation

Function getDesc
$0:="Database exposing employees and their companies"

This function can then be called:

$desc:=ds.getDesc() //"Database exposing..."

DataClass Class

Each table exposed with ORDA offers a DataClass class in the cs class store.

  • Extends: 4D.DataClass
  • Class name: cs.DataClassName (where DataClassName is the table name)
  • Example name: cs.Employee

Example

// cs.Company class


Class extends DataClass

// Returns companies whose revenue is over the average
// Returns an entity selection related to the Company DataClass

Function GetBestOnes()
$sel:=This.query("revenues >= :1";This.all().average("revenues"));
$0:=$sel

Then you can get an entity selection of the "best" companies by executing:

 var $best : cs.CompanySelection
$best:=ds.Company.GetBestOnes()

Example with a remote datastore

The following City catalog is exposed in a remote datastore (partial view):

The City Class provides an API:

// cs.City class

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

The client application opens a session on the remote datastore:

$cityManager:=Open datastore(New object("hostname";"127.0.0.1:8111");"CityManager")

Then a client application can use the API to get the city matching a zip code (for example) from a form:

Form.comp.city:=$cityManager.City.getCityName(Form.comp.zipcode)

EntitySelection Class

Each table exposed with ORDA offers an EntitySelection class in the cs class store.

  • Extends: 4D.EntitySelection
  • Class name: DataClassNameSelection (where DataClassName is the table name)
  • Example name: cs.EmployeeSelection

Example

// cs.EmployeeSelection class


Class extends EntitySelection

//Extract the employees with a salary greater than the average from this entity selection

Function withSalaryGreaterThanAverage
C_OBJECT($0)
$0:=This.query("salary > :1";This.average("salary")).orderBy("salary")

Then you can get employees with a salary greater than the average in any entity selection by executing:

$moreThanAvg:=ds.Company.all().employees.withSalaryGreaterThanAverage()

Entity Class

Each table exposed with ORDA offers an Entity class in the cs class store.

  • Extends: 4D.Entity
  • Class name: DataClassNameEntity (where DataClassName is the table name)
  • Example name: cs.CityEntity

Example

// cs.CityEntity class

Class extends Entity

Function getPopulation()
$0:=This.zips.sum("population")


Function isBigCity
C_BOOLEAN($0)
// The getPopulation() function is usable inside the class
$0:=This.getPopulation()>50000

Then you can call this code:

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 + " is a big city")
End if

Specific rules

When creating or editing data model classes, you must pay attention to the following rules:

  • Since they are used to define automatic DataClass class names in the cs class store, 4D tables must be named in order to avoid any conflict in the cs namespace. In particular:

    • Do not give the same name to a 4D table and to a user class name. If such a case occurs, the constructor of the user class becomes unusable (a warning is returned by the compiler).
    • Do not use a reserved name for a 4D table (e.g., "DataClass").
  • When defining a class, make sure the Class extends statement exactly matches the parent class name (remember that they're case sensitive). For example, Class extends EntitySelection for an entity selection class.

  • You cannot instantiate a data model class object with the new() keyword (an error is returned). You must use a regular method as listed in the Instantiated by column of the ORDA class table.

  • You cannot override a native ORDA class function from the 4D class store with a data model user class function.

Exposed vs non-exposed functions

For security reasons, all of your data model class functions are not exposed (i.e., private) by default to remote requests.

Remote requests include:

  • Requests sent by remote 4D applications connected through Open datastore
  • REST requests

Regular 4D client/server requests are not impacted. Data model class functions are always available in this architecture.

A function that is not exposed is not available on remote applications and cannot be called on any object instance from a REST request. If a remote application tries to access a non-exposed function, the "-10729 - Unknown member method" error is returned.

To allow a data model class function to be called by a remote request, you must explicitly declare it using the exposed keyword. The formal syntax is:

// declare an exposed function
exposed Function <functionName>

The exposed keyword can only be used with Data model class functions. If used with a regular user class function, it is ignored and an error is returned by the compiler.

Example

You want an exposed function to use a private function in a dataclass class:

Class extends DataClass

//Public function
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()

//Not exposed (private) function
Function computeIDNumber()-> $id : Integer
//compute a new ID number
$id:=...

When the code is called:

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() // Error "Unknown member method"

Local functions

By default in client/server architecture, ORDA data model functions are executed on the server. It usually provides the best performance since only the function request and the result are sent over the network.

However, it could happen that a function is fully executable on the client side (e.g., when it processes data that's already in the local cache). In this case, you can save requests to the server and thus, enhance the application performance by inserting the local keyword. The formal syntax is:

// declare a function to execute locally in client/server
local Function <functionName>

With this keyword, the function will always be executed on the client side.

The local keyword can only be used with data model class functions. If used with a regular user class function, it is ignored and an error is returned by the compiler.

Note that the function will work even if it eventually requires to access the server (for example if the ORDA cache is expired). However, it is highly recommended to make sure that the local function does not access data on the server, otherwise the local execution could not bring any performance benefit. A local function that generates many requests to the server is less efficient than a function executed on the server that would only return the resulting values. For example, consider the following function on the Schools entity class:

// Get the youngest students  
// Inappropriate use of local keyword
local Function getYoungest
var $0 : Object
$0:=This.students.query("birthDate >= :1"; !2000-01-01!).orderBy("birthDate desc").slice(0; 5)
  • without the local keyword, the result is given using a single request
  • with the local keyword, 4 requests are necessary: one to get the Schools entity students, one for the query(), one for the orderBy(), and one for the slice(). In this example, using the local keyword is inappropriate.

Examples

Calculating age

Given an entity with a birthDate attribute, we want to define an age() function that would be called in a list box. This function can be executed on the client, which avoids triggering a request to the server for each line of the list box.

On the StudentsEntity class:

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

Checking attributes

We want to check the consistency of the attributes of an entity loaded on the client and updated by the user before requesting the server to save them.

On the StudentsEntity class, the local checkData() function checks the Student's age:

Class extends Entity

local Function checkData() -> $status : Object

$status:=New object("success"; True)
Case of
: (This.age()=Null)
$status.success:=False
$status.statusText:="The birthdate is missing"

:((This.age() <15) | (This.age()>30) )
$status.success:=False
$status.statusText:="The student must be between 15 and 30 - This one is "+String(This.age())
End case

Calling code:

var $status : Object

//Form.student is loaded with all its attributes and updated on a Form
$status:=Form.student.checkData()
If ($status.success)
$status:=Form.student.save() // call the server
End if

Support in 4D projects

Class files

An ORDA data model user class is defined by adding, at the same location as regular class files (i.e. in the /Sources/Classes folder of the project folder), a .4dm file with the name of the class. For example, an entity class for the Utilities dataclass will be defined through a UtilitiesEntity.4dm file.

Creating classes

4D automatically pre-creates empty classes in memory for each available data model object.

By default, empty ORDA classes are not displayed in the Explorer. To show them you need to select Show all data classes from the Explorer's options menu:

ORDA user classes have a different icon from regular classes. Empty classes are dimmed:

To create an ORDA class file, you just need to double-click on the corresponding predefined class in the Explorer. 4D creates the class file and add the extends code. For example, for an Entity class:

Class extends Entity

Once a class is defined, its name is no longer dimmed in the Explorer.

Editing classes

To open a defined ORDA class in the 4D method editor, select or double-click on an ORDA class name and use Edit... from the contextual menu/options menu of the Explorer window:

For ORDA classes based upon the local datastore (ds), you can directly access the class code from the 4D Structure window:

Method editor

In the 4D method editor, variables typed as an ORDA class automatically benefit from autocompletion features. Example with an Entity class variable: