Saving Data

This chapter discusses the mechanics of saving data within the Enterprise Objects frameworks.

It is divided into the following sections:

Objects Involved in Saving

There are many objects involved in saving data in an Enterprise Objects application. The objects you’ll most commonly work with are introduced here.

EOEditingContext

In Enterprise Objects, saves to the database most often originate with a saveChanges invocation on an editing context.

EOObjectStoreCoordinator

This object is responsible for figuring out which data source corresponds to the changes in a particular editing context. A given editing context can contain enterprise objects that are constituted from various data sources, so the object store coordinator manages the logistics of identifying these data sources when saving.

EOCooperatingObjectStore

Each cooperating object store in an application is responsible for transmitting to its data source the changes handed to it by the object store coordinator. A cooperating object store (which is most often an instance of EODatabaseContext) is an abstract representation of a data source.

Other objects are involved in saving data, such as EODatabaseContext and EOAdaptorChannel, but you rarely need to interact with these objects programmatically.

Phases of Saving

The following phases are involved in pushing data back to a database:

Flow of Data During a Save

A save operation begins with the invocation of saveChanges on an editing context. If you programmatically create and manage editing contexts, you explicitly invoke this method. However, saveChanges is commonly invoked automatically by elements of an Enterprise Objects application such as display groups.

Figure 8-1 provides a high-level overview of the save process.

Figure 7-1  Flow of data during a save
Flow of data during a save

Once a save is initiated, the following sequence occurs to retrieve data from a data source:

  1. When saveChanges is invoked on an editing context, that editing context invokes the method editingContextWillSaveChanges on its editors and delegates.

  2. The editing context processes, propagates, and validates changes for deleting.

  3. The editing context processes and validates changes for saving.

  4. The editing context commits the changes made to its objects to its parent object store by invoking the method saveChangesInEditingContext on its parent. For nonnested editing contexts, the parent is typically an instance of EOObjectStoreCoordinator. When an object store coordinator receives this invocation, it guides its cooperating object stores through a multipass save protocol in which each cooperating store saves its own changes and forwards remaining changes to other cooperating stores.

  5. After it receives the invocation of saveChangesInEditingContext, the object store coordinator invokes the method prepareForSaveWithCoordinator on each of its cooperating object stores. This informs each object store that a multipass save operation is beginning. When the object store is an EODatabaseContext (which is the most common case), it initiates the process of generating primary keys for any new objects in the editing context on which saveChanges was invoked.

  6. The object store coordinator then invokes the method recordChangesInEditingContext on each of its cooperating object stores. This prompts each of those stores to examine the changed objects in the editing context, record any operations that need to be performed, and notify the coordinator of any changes that need to be forwarded to other cooperating stores.

    For example, if in its recordChangesInEditingContext method, a cooperating object store notices the removal of an object from an owning relationship but that object belongs to another cooperating store, it informs the other store by invoking forwardUpdateForObject on the object store coordinator.

  7. The object store coordinator invokes the method performChanges on each of its cooperating object stores. This tells the stores to transmit their changes to their underlying databases. When the cooperating store is an EODatabaseContext, it responds to this invocation by constructing a list of adaptor operations based on the list of database operations that were determined in the last step. It then forwards this list of adaptor operations on to an available EOAdaptorChannel to be executed.

  8. If performChanges fails for any of the cooperating object stores, the method rollbackChanges is invoked on all the cooperating object stores in that access-layer stack.

  9. If performChanges succeeds for all of the cooperating object stores, the method commitChanges is invoked on all of them, which tells the adaptor to commit its changes.

  10. If commitChanges fails for a particular cooperating store, the method rollbackChanges is invoked on that store and on all subsequent stores. However, the stores that have already committed their changes do not roll back. In other words, the object store coordinator doesn’t perform the two-phase commit necessary to guarantee consistent distributed updates.

  11. If the save operation is successful, the database contexts of the editing context from which the save operation originated update their snapshots.

  12. Finally, if the save operation is successful, the editing context posts the notification EditingContextDidSaveChangesNotification.

Key Generation

One of the best features of Enterprise Objects is its ability to manage primary and foreign keys for you. It does this in part by providing a mechanism to automatically generate primary keys without requiring you to write any code. To take advantage of Enterprise Object’s automatic primary-key generation, an entity’s primary key must satisfy these guidelines:

Automatic primary-key generation in Enterprise Objects works differently depending on the database, but you usually don’t need to be concerned with the details. With some databases such as OpenBase and MySQL, Enterprise Objects maintains a table in the database (EO_PK_TABLE) to help it maintain a linear sequence of nonrepeating primary keys. With an Oracle database, Enterprise Objects doesn’t need this table and instead uses Oracle sequences to generate primary keys.

The advantage of using Enterprise Object’s automatic primary-key generation is that it is easy to use and makes your life as a developer much simpler. However, there are some disadvantages and limitations, including these:

Fortunately, Enterprise Objects provides a number of hooks so you can provide primary key values. If an enterprise object or its snapshot already has a value for its primary key (which is the case for enterprise objects formed from preexisting data or for enterprise objects for which you set the primary key value explicitly in the enterprise object class), none of the key generation mechanisms are invoked. However, if this is not the case, the key generation mechanisms are given the opportunity to provide keys in this order:

  1. If you implement a delegate for EODatabaseContext and the delegate implements the method databaseContextNewPrimaryKey and if that method returns a value other than null, the keys it generates are used. An implementation of this feature is discussed in Generating Custom Primary Keys.

  2. If you provide to an entity the name of a stored procedure to use during Get PK operations (the operation type EOEntity.NextPrimaryKeyProcedureOperation), that stored procedure is invoked to generate a primary key. See the chapter “Working With Entities” in EOModeler User Guide to learn about automatic stored procedure invocation.

  3. If an entity’s primary key’s data type is binary and it conforms to EOTemporaryGlobalID.UniqueBinaryKeyLength, Enterprise Objects automatically generates a primary key. This is discussed in Using Binary Keys.

  4. The adaptor automatically generates a primary key for an object whose entity has a noncompound, simple integer primary key.

If the first mechanism fails to provide a primary key, the second mechanism is used. This continues through the last mechanism; if it fails to provide a primary key, an exception is thrown.

In the case of an entity that is the destination of a relationship that propagates primary key, the destination entity is assigned a primary key immediately after its source entity is assigned a key. So for destination entities in a relationship that propagates primary key, the key generation mechanisms in the list above are not invoked for that entity (though they may be invoked for the source entity).

See Generating Custom Primary Keys and Using Compound Primary Keys for sample implementations of custom key generation.

Common Delegate Usage

A number of control points are provided that let you customize save operations in Enterprise Objects applications. Table 8-1 lists the delegate methods in EOEditingContext that you can use to customize save operations in the control layer, and Table 8-2 lists the delegate methods in EODatabaseContext that you can use to customize save operations in the access layer.

You can set the delegate for each object by invoking the class method setDefaultDelegate.

Table 7-1  EOEditingContext delegate methods

Method

Description

editingContextShouldValidateChanges

This method is invoked when an EOEditingContext receives a saveChanges invocation. If this delegate method returns false, changes are saved without first performing validation. You can use this method to provide your own validation mechanism.

editingContextWillSaveChanges

This method is invoked when an EOEditingContext receives a saveChanges invocation. You can use this delegate method to perform custom validation before saving.

Table 7-2  EODatabaseContext delegate methods

Method

Description

databaseContextWillOrderAdaptorOperations

This method is invoked when an EODatabaseContext receives a performChanges invocation. You can use this delegate method to construct custom adaptor operations, such as transforming a delete operation into an update operation or into a stored procedure operation.

databaseContextWillPerformAdaptorOperations

This method is invoked within the performChanges method in EODatabaseContext. You can use this delegate method to provide custom ordering of adaptor operations to, for example, avoid violating any referential integrity constraints that are enforced by the database.

Generating Custom Primary Keys

There are many reasons why you may want to or need to generate custom primary keys in an Enterprise Objects application. They are discussed in Key Generation. The following sections provide sample implementations of two of the custom-key generation mechanisms, using the EODatabaseContext delegate and using the automatic key generation for binary primary keys.

Using a Delegate

The EODatabaseContext class provides a delegate in which you can generate custom primary keys. This is especially useful if an entity has a compound primary key, but it is also useful if your application can’t use Enterprise Object’s automatic key generation for simple integer primary keys.

You can set the delegate by invoking EODatabaseContext.setDefaultDelegate(this) in the class in which you implement the delegate method. An implementation of databaseContextNewPrimaryKey is shown in Listing 8-1.

Listing 7-1  databaseContextNewPrimaryKey implementation

public NSDictionary databaseContextNewPrimaryKey(EODatabaseContext dbCtxt, Object object, EOEntity entity) {
        NSArray rawRows = EOUtilities.rawRowsForSQL(new EOEditingContext(), "PKTester",          "SELECT MAX(FOO_PK) FROM FOO");
        NSDictionary rowWithPK = (NSDictionary)rawRows.objectAtIndex(0);
        Object maxPK = rowWithPK.objectForKey("FOO_PK");
        int pk = (new Integer(maxPK.toString())).intValue();
 
        NSMutableDictionary newPrimaryKey = new NSMutableDictionary();
        NSArray entityPrimaryKeys = entity.primaryKeyAttributeNames();
 
        Enumeration primaryKeyEnumerator = entityPrimaryKeys.objectEnumerator();
        while (primaryKeyEnumerator.hasMoreElements()) {
            String pkName = (String)primaryKeyEnumerator.nextElement();
            newPrimaryKey.takeValueForKey(new Integer(++pk), pkName);
        }
        return newPrimaryKey;
 }

The method in Listing 8-1 returns a dictionary of key-value pairs; the keys are the names of the entity’s primary key attributes (which are returned by the method EOEntity.primaryKeyAttributeNames) and the values are the values you generate in the method for each of those attributes.

In Listing 8-1, a SQL expression is sent to the database to determine the highest value for the entity’s primary key, FOO_PK. That value is stored in the pk variable, which is incremented in the while loop to generate a unique primary key.

Using Binary Keys

Enterprise Objects provides another useful mechanism to generate primary keys. It generates a binary primary key if a primary key meets these criteria:

  • Its external data type is a “raw bytes” data type, such as Oracle RAW and OpenBase binary.

  • Its internal data type is NSData.

  • Its internal data type width is 24 bytes.

The binary primary key generated in this case is a globally unique key based on an enterprise object’s EOTemporaryGlobalID. Generating primary keys this way has these advantages:

  • It doesn’t require a round trip to the database.

  • It is not database dependent.

  • The generated keys are globally unique.

Binary primary keys have the following characteristics, which can be considered disadvantages:

  • The generated keys are quite large.

  • Comparing keys requires more computing resources.

  • Binary keys can’t be part of a compound primary key.

Using Compound Primary Keys

Enterprise Objects supports tables that have compound primary keys. In EOModeler, you identify the attributes that are a part of the compound primary key by making those attributes primary keys (so the key icon is present in an attribute’s row). All of Enterprise Objects internal mechanisms that rely on primary keys (such as global ID creation) work just as well with compound primary keys as with simple primary keys. However, you cannot use a compound primary key and Enterprise Object’s automatic primary-key generation. You have to provide a primary key another way.

The easiest way to generate primary keys for compound primary keys is with the delegate method databaseContextNewPrimaryKey. The implementation of this method in Generating Custom Primary Keys supports a compound primary key. It retrieves all of an entity’s primary keys and generates a unique value for each one.

Note that methods in Enterprise Objects that return primary key values usually return an NSDictionary object. This is to support the case in which a primary key is compound. Each key of the dictionary is the name of an attribute that is part of the compound key.