Saving Objects
Saving Objects
To save an object to the database, invoke its %Save() method. For example:
Set obj = ##class(MyApp.MyClass).%New()
Set obj.MyValue = 10
Set sc = obj.%Save()
The %Save() method returns a %StatusOpens in a new tab value that indicates whether the save operation succeeded or failed. Failure could occur, for example, if an object has invalid property values or violates a uniqueness constraint; see Validating Objects.
Calling %Save() on an object automatically saves all modified objects that can be reached from the object being saved: that is, all embedded objects, collections, streams, referenced objects, and relationships involving the object are automatically saved if needed. The entire save operation is carried out as one transaction: if any object fails to save, the entire transaction fails and rolls back (no changes are made to disk; all in-memory object values are what they were before calling %Save()).
When an object is saved for the first time, the default behavior is for the %Save() method to automatically assign it an object ID value that is used to later find the object within the database. In the default case, the ID is generated using the $Increment function; alternately, the class can use a user-provided object ID based on property values that have an idkey index (and, in this case, the property values cannot include the string ||) . Once assigned, you cannot alter the object ID value for a specific object instance (even if it is a user-provided ID).
You can find the object ID value for an object that has been saved using the %Id() method:
// Open person "22"
Set person = ##class(Sample.Person).%OpenId(22)
Write "Object ID: ",person.%Id(),!
In more detail, the %Save() method does the following:
-
First it constructs a temporary structure known as a SaveSet. The SaveSet is simply a graph containing references to every object that is reachable from the object being saved. (Generally, when an object class A has a property whose value is another object class B, an instance of A can reach an instance of B.) The purpose of the SaveSet is to make sure that save operations involving complex sets of related objects are handled as efficiently as possible. The SaveSet also resolves any save order dependencies among objects.
As each object is added to the SaveSet, its %OnAddToSaveSet() callback method is called, if present.
-
It then visits each object in the SaveSet in order and checks if they are modified (that is, if any of their property values have been modified since the object was opened or last saved). If an object has been modified, it will then be saved.
-
Before being saved, each modified object is validated (its property values are tested; its %OnValidateObject() method, if present, is called; and then its uniqueness constraints are tested); if the object is valid, the save proceeds. If any object is invalid, then the call to %Save() fails and the current transaction is rolled back.
-
Before and after saving each object, the %OnBeforeSave() and %OnAfterSave() callback methods are called, if present. Similarly, any appropriately defined triggers are called as well.
These callbacks and any triggers are passed an Insert argument, which indicates whether an object is being inserted (saved for the first time) or updated.
If one of these callback methods or triggers fails (returns an error code) then the call to %Save() fails and the current transaction is rolled back.
-
Lastly, the %OnSaveFinally() callback method is called, if present.
This callback method is called after the transaction has completed and the status of the save has been finalized.
If the current object is not modified, then %Save() does not write it to disk; it returns success because the object did not need to be saved and, therefore, there is no way that there could have been a failure to save it. In fact, the return value of %Save() indicates that the save operation either did all that it was asked or it was unable to do as it was asked (and not specifically whether or not anything was written to disk).
In a multi-process environment, be sure to use proper concurrency controls; see Specifying Concurrency Options.
Rollback
The %Save() method automatically saves all the objects in its SaveSet as a single transaction. If any of these objects fail to save, then the entire transaction is rolled back. In this rollback, InterSystems IRIS does the following:
-
It reverts assigned IDs.
-
It recovers removed IDs.
-
It recovers modified bits.
-
It invokes the %OnRollBack() callback method, if implemented, for any object that had been successfully serialized.
InterSystems IRIS does not invoke this method for an object that has not been successfully serialized, that is, an object that is not valid.
Saving Objects and Transactions
As noted previously, the %Save() method automatically saves all the objects in its SaveSet as a single transaction. If any of these objects fail to save, then the entire transaction is rolled back.
If you wish to save two or more unrelated objects as a single transaction, however, you must enclose the calls to %Save() within an explicit transaction: that is, you must start the transaction using the TSTART command and end it with the TCOMMIT command.
For example:
// start a transaction
TSTART
// save first object
Set sc = obj1.%Save()
// save second object (if first was save)
If ($$$ISOK(sc)) {
Set sc = obj2.%Save()
}
// if both saves are ok, commit the transaction
If ($$$ISOK(sc)) {
TCOMMIT
}
There are two things to note about this example:
-
The %Save() method knows if it is being called within an enclosing transaction (because the system variable, $TLEVEL, will be greater than 0).
-
If any of the %Save() methods within the transaction fails, the entire transaction is rolled back (the TROLLBACK command is invoked). This means that an application must test every call to %Save() within a explicit transaction and if one fails, skip calling %Save() on the other objects and skip invoking the final TCOMMIT command.