Skip to main content

Working with Collections

Caché supports collections, which provide a way to work with a set of elements, all of the same type. The elements can be literal values or can be objects.

You can define collection properties in any object class. You can also define stand-alone collections for other purposes, such as for use as an method argument or return value. This chapter describes collections, especially collection properties. It discusses the following topics:

Also see the chapters “Defining and Using Literal Properties,” “Working with Streams,” “Defining and Using Object-Valued Properties,” “Defining and Using Relationships”, and “Using and Overriding Property Methods.”

When viewing this book online, use the preface of this book to quickly find other topics.

Introduction to Collections

A collection contains a set of individual elements, all of the same type. There are two kinds of collections: lists and arrays.

Each item in a collection is called an element and its position within the collection is called a key. For list collections, the system generates sequential integer keys. For arrays, keys can have arbitrary values, and you specify them for each element.

Caché uses a set of collection classes as an interface to collection properties; these are classes in the %Collection package. Caché provides a different set of collection classes for use when you need a stand-alone collection, for example, to pass as an argument to a method; these are classes in the %Library package.

Each set of classes provides methods and properties that you can use to add collection items, remove collection items, count collection items, and so on. This chapter focuses on %Collection classes, but the details are similar for the %Library classes.

Note that collection classes are object classes. Thus a collection is an object.

Defining Collection Properties

To define a list property, add a property as follows:

Property MyProp as List of Type;

Where MyProp is the property name, and Type is either a data type class or an object class.

Similarly, to define an array property, add a property as follows:

Property MyProp as Array of Type;

For example, the following property definition is a list of %StringOpens in a new tab values:

Property Colors As List Of %String;

For another example, the following property definition is an array of Doctor values, where Doctor is the name of an object class.

Property Doctors As Array Of Doctor;

Internally, Caché uses classes in the %Collection package to represent such properties, as follows:

This means that you use methods of these classes to add collection items, remove collection items, and so on. Later parts of this chapter show how this is done.

Do not use the %Collection classes directly as the type of a property. For example, do not create a property definition like this:

Property MyProp as %Collection.ArrayOfDT;

Instead use the syntax shown earlier in this section.

Adding Items to a List Property

Given a list property (as described in the previous section), use the following procedure to specify a value for the property:

  1. If the list items are objects, create those objects as needed.

  2. Add list items to the list as needed. To add one list item, call the Insert() instance method of the list property. This method is as follows:

    method Insert(listitem) as %Status
    

    Or use other methods of the list property, such as InsertAt(). For an introduction, see “Working with List Properties.”

    For details on the methods, see the class reference for %Collection.ListOfDTOpens in a new tab and %Collection.ListOfObjOpens in a new tab.

For example, suppose that obj is an OREF, and Colors is a list property of the associated object. In that case, we could add list items as follows:

 Do obj.Colors.Insert("Red")
 Do obj.Colors.Insert("Green")
 Do obj.Colors.Insert("Blue")

For another example, suppose that pat is an OREF, and Diagnoses is a list property of the associated object. This property is defined as follows, where PatientDiagnosis is the name of a class:

Property Diagnoses as list of PatientDiagnosis;

In this case, we could add a list item as follows:

 Set patdiag=##class(PatientDiagnosis).%New()
 Set patdiag.DiagnosisCode=code
 Set patdiag.DiagnosedBy=diagdoc
 Set status=pat.Diagnoses.Insert(patdiag)

Adding Items to an Array Property

Given an array property (as described earlier in this chapter), use the following procedure to specify a value for the property:

  1. If the array items are objects, create those objects as needed.

  2. Add array items to the array as needed. To add one list item, call the SetAt() instance method of the array property. This method is as follows:

    method SetAt(element, key As %String) as %Status
    

    Where element is the element to add, and key is the array key to associate with that element.

    Important:

    Do not include a sequential pair of vertical bars (||) within the value that you use as the array key. This restriction is imposed by the way in which the Caché SQL mechanism works.

    For details on this method, see the class reference for %Collection.ArrayOfDTOpens in a new tab and %Collection.ArrayOfObjOpens in a new tab. (You will notice that these classes define the same set of methods.)

    Or use the other methods described in “Working with Array Properties.”

For example, to add a new color to an array of RGB values accessed by color name in a Palette object, use the following code:

 Do palette.Colors.SetAt("255,0,0","red")

where palette is the OREF containing the array, Colors is the name of the array property, and “red” is the key to access the value “255,0,0”.

Working with List Properties

When you create a list property as described earlier, the property itself is an object that provides the instance methods of one of the following classes, depending on the property definition:

These classes provide instance methods such as GetAt(), Find(), GetPrevious(), GetNext(), and Remove(). The following example shows how you might use these methods:

 set p=##class(Sample.Person).%OpenId(1)
 for i=1:1:p.FavoriteColors.Count() {
    write !, p.FavoriteColors.GetAt(i)
 }

Lists are ordered collections of information. Each list element is identified by its position (slot) in the list. You can set the value for a slot or insert data at a slot. If you set a new value for a slot, that value is stored in the list. If you set the value for an already existing slot, the new data overwrites the previous data and the slot assignments are not modified. If you insert data at an already existing slot, the new list item increments the slot number of all subsequent slots. (Inserting a new item in the second slot slides the data currently in the second slot to the third slot, the object currently in the third slot to the fourth slot, and so on.)

You can modify data at slot n using the following syntax:

 Do oref.PropertyName.SetAt(data,n)

where oref is an OREF, PropertyName is the name of a list property of that object, and data is the actual data. For example, suppose that person.FavoriteColors is a list of favorite colors and suppose that this list is initially “red”,“blue”, and “green.” To change the second color in the list (so that the list is “red”,“yellow”, and “green”), we can use the following code:

 Do person.FavoriteColors.SetAt("yellow",2)

For other methods, such as Find(), RemoveAt(), and others, see the class reference for %Collection.ListOfDTOpens in a new tab and %Collection.ListOfObjOpens in a new tab.

Working with Array Properties

When you create an array property as described earlier, the property itself is an object that provides the instance methods of one of the following classes, depending on the property definition:

These classes provide instance methods such as GetAt(), Find(), GetPrevious(), GetNext(), and Remove(). For details, see the class reference for these classes. Note that the details are not the same as for the list classes.

Copying Collection Data

To copy the items in one collection into another collection, set the recipient collection equal to the source collection. This copies the contents of the source into the recipient (not the OREF of the collection itself). Some examples of such a command are:

 Set person2.Colors = person1.Colors
 Set dealer7.Inventory = owner3.cars

where person2, person1, dealer7, and owner3 are all instances of classes and Colors, Inventory, and cars are all collection properties. The first line of code looks as it might for copying data between two instances of a single class and the second line of code as it might for copying data from an instance of one class to an instance of a different class.

If the recipient collection is a list and the source collection is an array, Caché copies only the data of the array (not its key values). If the recipient collection is an array and the source collection is a list, the Caché generates key values for the recipient array; these key values are integers based on the position of the item in the source list.

Note:

There is no way to copy the OREF from one collection to another. It is only possible to copy the data.

Controlling the SQL Projection of Collection Properties

As described earlier in this book, a persistent class is projected as an SQL table. This section describes how list and array properties are projected by default and how you can modify those SQL projections.

Default Projection of List Properties

By default, a list property is projected to SQL as a $LIST in serialized form. This means that when you obtain such a value, you should use functions suitable for $LIST in order to work with it. The following example obtains the value of a list property via embedded SQL and then uses suitable functions to work with the value:

 &sql(SELECT favoritecolors INTO :FavCol FROM Sample.Person WHERE id=1)
 write !, $LISTVALID(FavCol)
 for i=1:1:$LISTLENGTH(FavCol) {
    write !, $LIST(FavCol,i)
 }

If the list for a particular instance contains no elements, it is projected as an empty string (and not an SQL NULL value).

Default Projection of Array Properties

By default, an array property is projected as a child table, which is in the same package as the parent table. The name of this child table is as follows:

tablename_fieldname

Where

  • tablename is the SqlTableName of the parent class (if specified) or the short name of the parent class (if SqlTableName is not specified).

  • fieldname is the SqlFieldName of the array property (if specified) or the name of the array property (if SqlFieldName is not specified).

For example, a Person class with an array property called Siblings has a projection as a child table called “Person_Siblings”.

The child table contains the following three columns:

  • One contains the ID of the corresponding instance of the parent class; the name of this column is that of the class containing the array (Person, in the example).

  • One contains the identifier for each array member; its name is always element_key.

  • One contains array members for all the instances of the class; its name is that of array property (Siblings, in the example).

Continuing the example of the Person class with an array property called Siblings, the projection of Person includes a Person_Siblings child table with the following entries:

Sample Projection of an Array Property
Person (ID) element_key Siblings
10 C Claudia
10 T Tom
12 B Bobby
12 C Cindy
12 G Greg
12 M Marsha
12 P Peter

If an instance of the parent class holds an empty collection (one that contains no elements), the ID for that instance does not appear in the child table, such as the instance above where ID equals 11.

Notice that there is no Siblings column in the parent table.

For the column(s) containing the array members, the number and contents of the column(s) depend on the kind of array:

  • The projection of an array of data type properties is a single column of data.

  • The projection of an array of reference properties is a single column of object references.

  • The projection of an array of embedded objects is as multiple columns in the child table. The structure of these columns is described in the section “Embedded Object Properties.”

Together, the ID of each instance and the identifier of each array member comprise a unique index for the child table. Also, if a parent instance has no array associated with it, it has no associated entries in the child table.

Note:

A serial object property is projected to SQL in the same way, by default.

Important:

When a collection property is projected as an array, there are specific requirements for any index you might add to the property. See “Indexing Collections” in Caché SQL Optimization Guide. For an introduction to indices in Caché persistent classes, see the chapter “Other Options for Persistent Classes.”

Important:

There is no support for SQL triggers on child tables projected by array collections. However, if you update the array property and then save the parent object using ObjectScript, any applicable triggers will fire.

Alternative Projections of Collections

This section discusses the STORAGEDEFAULT, SQLTABLENAME, and SQLPROJECTION property parameters, which affect how collection properties are stored and projected to SQL.

STORAGEDEFAULT Parameter

You can store a list property as a child table, and you can store an array property as a $LIST. In both cases, you specify the STORAGEDEFAULT parameter of the property:

  • For a list property, STORAGEDEFAULT is "list" by default. If you specify STORAGEDEFAULT as "array", then the property is store and projected as a child table. For example:

    Property MyList as list of %String (STORAGEDEFAULT="array");
    

    For details on the resulting projection, see “Default Projection of Array Properties.”

  • For an array property, STORAGEDEFAULT is "array" by default. If you specify STORAGEDEFAULT as "list", then the property is stored and projected as a $LIST. For example:

    Property MyArray as array of %String (STORAGEDEFAULT="list");
    

    For details on the resulting projection, see “Default Projection of List Properties.”

Important:

The STORAGEDEFAULT property parameter affects how the compiler generates storage for the class. If the class definition already includes a storage definition for the given property, the compiler ignores this property parameter.

SQLTABLENAME Parameter

If a collection property is projected as a child table, you can control the name of that table. To do so, specify the SQLTABLENAME parameter of the property. For example:

Property MyArray As array Of %String(SQLTABLENAME = "MyArrayTable");

Property MyList As list Of %Integer(SQLTABLENAME = "MyListTable", STORAGEDEFAULT = "array");

The SQLTABLENAME parameter has no effect unless the property is projected as a child table.

SQLPROJECTION Parameter

By default, if a collection property is stored as a child table, it is also projected as a child table, but it is not available in the parent table. To make such a property also available in the parent table, specify the SQLPROJECTION parameter of the property as "table/column"

For example, consider the following class definition:

Class Sample.Sample Extends %Persistent
{

Property Property1 As %String;

Property Property2 As array Of %String(SQLPROJECTION = "table/column");

}

The system generates two tables for this class: Sample.Sample and Sample.Sample_Property2

The table Sample.Sample_Property2 stores the data for the array property Property2, as in the default scenario. Unlike the default scenario, however, a query can refer to the Property2 field in the Sample.Sample table. For example:

SAMPLES>>SELECT Property2 FROM Sample.Sample where ID=7
13.     SELECT Property2 FROM Sample.Sample where ID=7
 
Property2
"1     value 12       value 23       value 3"

The SELECT * query, however, does not return the Property2 field:

SAMPLES>>SELECT * FROM Sample.Sample where ID=7
14.     SELECT * FROM Sample.Sample where ID=7
 
ID      Property1
7       abc
Note:

There are other possible values of the SQLPROJECTION property parameter, but those values have an effect only in MV-enabled classes.

Creating and Using Stand-Alone Collections

The following classes are meant for use as collections that are not class properties:

To create a stand-alone collection, call the %New() method of the suitable class to obtain an instance of that class. Then use methods of that instance to add elements and so on. For example:

 set mylist=##class(%ListOfDataTypes).%New()
 do mylist.Insert("red")
 do mylist.Insert("green")
 do mylist.Insert("blue")
 write mylist.Count()

These classes provide methods with many of the same names as the other collection classes. For details, see the class reference.

FeedbackOpens in a new tab