Filed under: Best Practices, Core Updates
Comments: 1
All Business Model Classes share a standard object lifecycle of new, create, read, update, delete, and more. This article provides examples and details of how the read lifecycle event and its parameters work.
This information is available in other documents and help, but it is so important, I’ve put it here as well. You can find the latest info in the dynamic help of NexJ Studio by hovering over the read statement in a script editor. I’ll go into more detail in other posts.
read(attributes where orderBy count offset xlock): instance-collection
Reads specified instances of this class. The main action is implemented by the framework.
Arguments
attributes list The list of attributes, or associated objects, to proactively retrieve. ‘() retrieves no attributes and any attributes accessed after such a read will trigger a lazy load. See examples for special downcast (@@) syntax and annotation syntax.
where list The where clause. See examples for information on associations, reverse associations and qualified associations.
orderBy list The expressions on which to order the collection – a list of (<sort-expr> . <ascending?>) pairs.
count integer The number of instances to retrieve. Null ‘() for the default value of -1, meaning up to the readLimit specified in the data source.
offset integer The number of top level instances to skip from the beginning of the retrieved collection. Null ‘() for the default value of 0.
xlock boolean If true, an exclusive lock is set in persistent storage on the matching top level objects until the transaction is finished. null ‘() for the default value of #f.
Returns
instance-collection Collection of instances matching the where clause.
Detail
Associated objects can be retrieved using nested lists in the attributes argument. Simply specify a non-primitive attribute name then a list of one or more attributes. e.g. (Entity’read ‘(firstName lastName (addrs city state zip)) These nestings can continue… e.g. (Entity’read ‘(firstName lastName (addrs city state zip (type name)))
Attributes can also be retrieved polymorphically – meaning attributes that only exist on subclasses can be retrieved when reading the base class. (see examples)
Attribute names in the where and order by clauses may take the simple form of <attributeName> or (@ <attributeName>). The second form is recommended. In this case the “@” represents the current instance and may be used to traverse arbitrarily long association paths. e.g. on User we may specify (= (@ person homeAddress city) “Toronto”)
One cannot pass in a larger count than readLimit defined in the data source. A count of -1 will not restrict the count, but if more than readLimit instances are returned, an error will be thrown. In cases where more than readLimit instances are desired, the openCursor method should be used. The helper function for-each-page can normally be used and provides a simplified API to openCursor.
Examples
1 |
(Person'read '(firstName lastName) '(and ( = (@ firstName) "Joe") (= (@ lastName) "Test")) '(((@ firstName) . #t) ((@ lastName) . #f)) 1 0 #f) |
=> ; #<[]> if Joe doesn’t exist
=> ; #<[Instance<Person, OID:1:V32:4AA92BA6E6A64CE5BBEB8… if Joe does exist
More advanced read
1 2 3 4 5 |
(define fn "Joe") (Person'read '(firstName lastName (addrs city state (type name))) ; read in city and state attributes as well as the type and it's name for all of Joe's addresses `(and ( = (@ firstName) ,fn) (= (@ lastName) "Test")) ; variable substitution for the value of the firstName '((firstName . #t) (lastName . #f)) 1 0 #f) ; order by firstName ascending then lastName descending |
Use of the any operator – Reduces the multiplicity of an association to 1
1) Expressing queries that are impossible to do only with joins
In this example, return people who have at least one address in Toronto and at least one in Richmond Hill. Nothing will be returned by the equals operator equivalent (Person’read … ‘(and (= (@ addrs city) “Toronto”) (= (@ addrs city) “Richmond Hill”)) …)
1 2 3 4 |
(Person'read '(firstName) '(and (any (= (@ addrs city) "Toronto")) (any (= (@ addrs city) "Richmond Hill"))); two part association path '() '() '() '()) |
2) Optimizing database filtering on collection associations
In this example, database will return only one person record per macthing addresses vs returning as many copies of a person record as there are matching addresses when using the equals operator equivalent (Person’read … ‘(= (@ addrs city) “Toronto”) …)
1 2 3 4 |
(Person'read '(firstName) '(any (= (@ addrs city) "Toronto"); two part association path '() '() '() '()) |
Annotations in the attributes list – create calculated attributes on-the-fly
In this example, return the lastName attribute, a calculated _lnLen attribute which is the length of the last name, and a _fullName calculated attribute
We use the underscore here just to avoid name collisions.
1 2 3 |
(Person'read '(lastName (: _lnLen (string-length (@ lastName))) (: _fullName (string-affix (@ lastName) ", " (@ firstName)))) '() '() '() '() '()) |
Aggregate functions – can be applied to subcollections on instances
Supported operators include count, average, min, max, sum
For aggregation at the top level of the class, see the aggregate method
1 2 3 4 5 6 7 8 9 10 |
(Person'read '(firstName lastName (: _countryCnt (count (@ addrs country))) (: _uniqueCountryCnt (count (unique (@ addrs country)))) (: _nullCodeCnt (count (null? (@ addrs zip)))) (: _avg (average (string-length (@ addrs city)))) (: _min (minimum (string-length (@ addrs city)))) (: _max (maximum (@ addrs city))) (: _sum (sum (string-length (@ addrs city))))) '() '() '() '() '()) |
Attribute downcast syntax (@@) – Polymorphic read
Used to proactively load subclass specific attributes when reading from a base class
1 2 3 4 5 6 |
(Entity'read '(fullName lastName (@@ Person homePhone)) ; only retrieve the homePhone attribute for the Person subclass of Entity `() '(((@ lastName) . #t)) 100 ; read only the first 100 instances 0 #f) |
Conditions in where clause association paths
This syntax allows us to restrict parts of association paths to specific subclasses and also to apply arbitrary conditions on association paths
1) Most common use case: limit an association based on a qualifier e.g. look for all Entities with a user named “Shaw” playing their advisor role
1 2 3 4 |
(Entity'read '(fullName) '(= (@ coverage (= (@ coverageRole roleName) "Advisor") userPerson lastName) "Shaw") '() '() '() '()) |
2) Subclass restriction example: note that homeAddress is an attribute that is only found on the Person subclass of Entity
1 2 3 4 |
(Telcom'read '(fullName) '(= (@ entity (instance? (@) Person) homeAddress city) "Toronto") '() '() '() '()) |
3) Arbitrary condition example: reads all instances of the Telcom class that has an entity with a lastname of “Shaw” that has an address in “Toronto” with and address2 line that is not null.
1 2 3 4 |
(Telcom'read '(fullName) '(not (null? (@ entity (= (@ lastName) "Shaw") addrs (= (@ city) "Toronto") address2))) '() '() '() '()) |
Note that conditions may be applied in reverse associations as well as forward associations
Where Clause reverse association syntax (@@)
Used to query from the end of an association “back” to the class you are reading.
Forward associations take the form (@ part1 part2 part3 …). The @ represents an instance of the class being read.
Reverse associations take the form (@@ <ClassName> part3 part2 part1) where part1 represents an attribute with a type of the class being read.
You can usually just navigate the forward association, but you may see this syntax if you are tracing requests from NexJ UI clients
1 2 |
(Address'read '() `(= (@@ User person addrs) ,(user)) '() '() '() '()) ; reverse from Address. note that this is equivalent to (((user)'person)'addrs) (Address'read '() `(= (@ entity user) ,(user)) '() '() '() '()) ; forward from Address is also equivalent |
About heterogeneous joins
Definitions:
Root class – Metaclass on which a read event is invoked.
Root datasource – Datasource to which a root class is persisted.
Heterogeneous join – A query contains a heterogeneous join when it references an attribute not stored in the root datasource.
Homogeneous association – An association is homogeneous if it can be retrieved without accessing another datasource. This is possible at present if its source key in the root datasource references a primary key in the another datasource (i.e. only the OID of the object is stored in the root data source).
Restrictions:
The association used for filtering and/or sorting must be homogeneous.
For the examples, assume the following:
1. Root class – ConversationUser
2. Root datasource – Conversation
3. Other datasource – DefaultRelational
4. ‘from’ is an association on ConversationUser referencing a class in the other datasource
1) Filtering
1 2 3 4 5 6 7 8 9 10 11 |
; Works (ConversationUser'read '((from firstName)) `(= (@ from) ,(oid #z00000000000010008000BEEF0000000D)) ; Homogeneous since it filters on the OID (i.e. fromId column on table ConvUser in Conversation datasource) '() '() '() '()) ; Doesn't work (ConversationUser'read '((from fristName)) '(= (@ from firstName) "nexjsa") ; Not homogeneous since it would have to filter on the firstName column on Entity in DefaultRelational datasource '() '() '() '()) |
2) Sorting
1 2 3 4 5 6 7 8 9 10 11 12 13 |
; Works (ConversationUser'read '(subject) '() '(((@ from) . #t)) ; Homogeneous since it sorts on the OID (ie. fromId column on ConvUser in table ConvUser in Conversation datasource) '() '() '()) ; Doesn't work (ConversationUser'read '(subject) '() '(((@ from firstName) . #t)) ; Not homogeneous since it would have to sort on the firstName column on Entity in DefaultRelational datasource '() '() '()) |
See Also
read-instance aggregate openCursor openAggregateCursor for-each-page