Skip to main content

Defining and Using Object-Valued Properties

This chapter describes how to define and use object-valued properties, including serial object properties. It discusses the following topics:

Relationships provide another way to associate different persistent classes; see the chapter “Relationships.” Also see the chapters “Defining and Using Literal Properties,” “Working with Collections,” “Working with Streams,” and “Using and Overriding Property Methods.”

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

Defining Object-Valued Properties

The phrase object-valued property generally refers to a property that is defined as follows:

Property PropName as Classname;

Where Classname is the name of an object class other than a collection or a stream. (Collection properties and stream properties are special cases discussed in earlier chapters.) In general, Classname is either a registered object class, a persistent class, or a serial class (see the next section).

To define such a property, define the class to which the property refers and then add the property.

Variation: CLASSNAME Parameter

If a property is based on a persistent class, and that class uses the alternative projection of subclasses described in the chapter “Defining Persistent Classes,” an additional step is necessary. In this case, it is necessary to specify the CLASSNAME property parameter as 1 for that property. This step affects how Caché stores this property and enables Caché to retrieve the object to which it points.

For example, suppose that MyApp.Payment specifies NoExtent, and MyApp.CreditCard is a subclass of MyApp.Payment. Suppose that MyApp.CurrencyOrder contains a property of type MyApp.CreditCard. That property should specify CLASSNAME as 1:

Class MyApp.CurrencyOrder [ NoExtent ] 
{
Property Payment as MyApp.CreditCard (CLASSNAME=1);

//other class members
}

Note that SQL arrow syntax does not work in this scenario. (You can instead use a suitable JOIN.)

Important:

Do not specify CLASSNAME =1 for a property whose type is a serial class. This usage is not supported.

Introduction to Serial Objects

Serial classes extend %SerialObjectOpens in a new tab. The purpose of such classes is to serve as a property in another object class. The values in a serial object are serialized into the parent object. Serial objects are also called embedded (or embeddable) objects. Caché handles serial object properties differently from non-serial object properties. Two of the differences are as follows:

  • It is not necessary to call %New() to create the serial object before assigning values to properties in it.

  • If the serial object property is contained in a persistent class, the properties of the serial object are stored within the extent of the persistent class.

Later sections of this chapter show these points.

To define a serial class, simply define a class that extends %SerialObjectOpens in a new tab, and add properties and other class members as needed. The following shows an example:

Class Sample.Address Extends %SerialObject
{

/// The street address.
Property Street As %String(MAXLEN = 80);

/// The city name.
Property City As %String(MAXLEN = 80);

/// The 2-letter state abbreviation.
Property State As %String(MAXLEN = 2);

/// The 5-digit U.S. Zone Improvement Plan (ZIP) code.
Property Zip As %String(MAXLEN = 5);

}

Possible Combinations of Objects

The following table shows the possible combinations of a parent class and an object-valued property in that class:

  Property is a registered object class Property is a persistent class Property is a serial class
Parent class is a registered object class Supported Supported but not common Supported
Parent class is a persistent class Supported but not common Supported Supported
Parent class is a serial class Not supported Not supported Supported

Terms for Object-Valued Properties

Within a persistent class, there are two terms for object-valued properties:

  • Reference properties (properties based on other persistent objects)

  • Embedded object properties (properties based on serial objects)

Relationships are another kind of property that associates different persistent classes; see the chapter “Relationships.” Relationships are bidirectional, unlike the properties described in this chapter.

Specifying the Value of an Object Property

To set an object-valued property, set that property equal to an OREF of an instance of a suitable class.

Consider the scenario where ClassA contains a property PropB that is based on ClassB, where ClassB is an object class:

Class MyApp.ClassA
{

Property PropB as MyApp.ClassB;

//additional class members
}

And ClassB has a non-serial class with its own set of properties Prop1, Prop2, and Prop3.

Suppose that MyClassAInstance is an OREF for an instance of ClassA. To set the value of the PropB property for this instance, do the following:

  1. If ClassB is not a serial class, first:

    1. Obtain an OREF for an instance of ClassB.

    2. Optionally set properties of this instance. You can also set them later.

    3. Set MyClassAInstance.PropB equal to that OREF.

    You can skip this step if ClassB is a serial class.

  2. Optionally use cascading dot syntax to set properties of the property (that is, to set properties of MyClassAInstance.PropB).

For example:

 set myclassBInstance=##class(MyApp.ClassB).%New()
 set myClassBInstance.Prop1="abc"
 set myClassBInstance.Prop2="def"
 set myClassAInstance.PropB=myclassBInstance
 set myClassAInstance.PropB.Prop3="ghi"

Notice that this example sets properties of the ClassB instance directly, right after the instance is created, and later more indirectly via cascading dot syntax.

The following steps accomplish the same goal:

 set myClassAInstance.PropB=##class(MyApp.ClassB).%New()
 set myClassAInstance.PropB.Prop1="abc"
 set myClassAInstance.PropB.Prop2="def"
 set myClassAInstance.PropB.Prop3="ghi"

In contrast, if ClassB is a serial class, you can do the following, without ever calling %New() for ClassB:

 set myClassAInstance.PropB.Prop1="abc"
 set myClassAInstance.PropB.Prop2="def"
 set myClassAInstance.PropB.Prop3="ghi"

Saving Changes

In the case where you are using persistent classes, save the containing object (that is, the instance that contains the object property). There is no need to save the object property directly, because that is saved automatically when the containing object is saved.

The following examples demonstrate these principles. Consider the following persistent classes:

Class MyApp.Customers Extends %Persistent
{

Property Name As %String;

Property HomeStreet As %String(MAXLEN = 80);

Property HomeCity As MyApp.Cities;

}

And:

Class MyApp.Cities Extends %Persistent
{

Property City As %String(MAXLEN = 80);

Property State As %String;

Property ZIP As %String;

}

In this case, we could create an instance of MyApp.Customers and set its properties as follows:

    set customer=##class(MyApp.Customers).%New()
    set customer.Name="O'Greavy,N."
    set customer.HomeStreet="1234 Main Street"
    set customer.HomeCity=##class(MyApp.Cities).%New()
    set customer.HomeCity.City="Overton"
    set customer.HomeCity.State="Any State"
    set customer.HomeCity.ZIP="00000"
    set status=customer.%Save()
    if $$$ISERR(status) {
        do $system.Status.DisplayError(status)
    }

These steps add one new record to MyApp.Customers and one new record to MyApp.Cities.

Instead of calling %New() for MyApp.Cities, we could open an existing record:

    set customer=##class(MyApp.Customers).%New()
    set customer.Name="Burton,J.K."
    set customer.HomeStreet="17 Milk Street"
    set customer.HomeCity=##class(MyApp.Cities).%OpenId(3)
    set status=customer.%Save()
    if $$$ISERR(status) {
        do $system.Status.DisplayError(status)
    }

In the following variation, we open an existing city and modify it, in the process of adding the new customer:

    set customer=##class(MyApp.Customers).%New()
    set customer.Name="Emerson,S."
    set customer.HomeStreet="295 School Lane"
    set customer.HomeCity=##class(MyApp.Cities).%OpenId(2)
    set customer.HomeCity.ZIP="11111"
    set status=customer.%Save()
    if $$$ISERR(status) {
        do $system.Status.DisplayError(status)
    }

This change would of course be visible to any other customers with this home city.

SQL Projection of Object-Valued Properties

As described earlier in this book, a persistent class is projected as an SQL table. This section describes how reference properties and embedded object properties of such a class are projected to SQL.

Reference Properties

A reference property is projected as a field that contains the ID portion of the OID of the referenced object. For instance, suppose a customer object has a Rep property that refers to a SalesRep object. If a particular customer has a sales representative with an ID of 12, then the entry in the Rep column for that customer is also 12. Because this value matches that of the particular row of the ID column of the referenced object, you can use this value when performing any joins or other processing.

Note that within Caché SQL, you can use a special reference syntax to easily use such references, as an alternative to using a JOIN. For example:

SELECT Company->Name FROM Sample.Employee ORDER BY Company->Name

Embedded Object Properties

An embedded object property is projected as multiple columns in the table of the parent class. One column in the projection contains the entire object in serialized form (including all delimiters and control characters). The rest of the columns are each for one property of the object.

The name of the column for the object property is the same as that of the object property itself. The other column names are made up of the name of the object property, an underscore, and the property within the embedded object. For instance, suppose a class has a Home property containing an embedded object of type Address; Home itself has properties that include Street and Country. The projection of the embedded object then includes the columns named “Home_Street” and “Home_Country”. (Note that the column names are derived from the property, Home, and not the type, Address.)

For example, the sample class Sample.PersonOpens in a new tab, includes a Home property which is an embedded object of type Sample.AddressOpens in a new tab. You can use the component fields of Home via SQL as follows:

SELECT Name, Home_City, Home_State FROM Sample.Person 
WHERE Home_City %STARTSWITH 'B'
ORDER BY Home_City

Embedded objects can also include other complex forms of data:

  • The projection of a reference property includes a read-only field that includes the object reference as described in “Reference Properties.”

  • The projection of an array is as a single, non-editable column that is part of the table.

  • The projection of a list is as a list field as one of its projected fields; the list field is as described in “Default Projection of List Properties.”

FeedbackOpens in a new tab