Table of Contents Previous Section
Saving Changes
In a running application, the changes users make to objects are reflected in the object graph managed by an EOEditingContext and in the user interface. However, the database isn't updated to reflect these changes until there is an explicit request (typically issued by the user) to save. The sequence of events in a save operation is illustrated in Figure 52.
Figure 52. Saving to the Database
- The editing context sends it editors and delegates the message editingContextWillSaveChanges.
- The editing context processes, propagates, and validates deletes.
- The editing context processes and validates changes for saving.
- The editing context commits the changes made to its objects to its parent object store by sending the parent the message saveChangesInEditingContext (saveChangesInEditingContext: in Objective-C). If the editing context is not nested, its parent is typically an EOObjectStoreCoordinator. When an EOObjectStoreCoordinator receives this message, it guides its EOCooperatingObjectStores through a multi-pass save protocol in which each cooperating store saves its own changes and forwards remaining changes to other cooperating stores.
- After it receives the message saveChangesInEditingContext, the object store coordinator sends each of its cooperating stores a prepareForSaveWithCoordinator message (prepareForSaveWithCoordinator:editingContext: in Objective-C), which informs them that a multi-pass save operation is beginning. When the cooperating store is an EODatabaseContext, it takes this opportunity to generate primary keys for any new objects in the editing context.
- The coordinator sends each of its cooperating stores the message recordChangesInEditingContext, which prompts them 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 one cooperating 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 sending the coordinator a forwardUpdateForObject message (forwardUpdateForObject:changes: in Objective-C).
- The coordinator sends each of its cooperating stores the message performChanges. This tells the stores to transmit their changes to their underlying databases. When the cooperating store is an EODatabaseContext, it responds to this message by taking the EODatabaseOperations that were constructed in the previous step, constructing EOAdaptorOperations from them, and giving the EOAdaptorOperations to an available EOAdaptorChannel for execution.
- If performChanges fails for any of the EOCooperatingObjectStores, all stores are sent the message rollbackChanges.
- If performChanges succeeds for all EOCooperatingObjectStores, the receiver sends them the message commitChanges, which has the effect of telling the adaptor to commit the changes.
- If commitChanges fails for a particular cooperating store, that store and all subsequent ones are sent the message rollbackChanges. However, the stores that have already committed their changes do not roll back. In other words, the EOObjectStoreCoordinator doesn't perform the two-phase commit protocol necessary to guarantee consistent distributed update.
- If the save operation was successful, the editing context updates its object snapshots.
- Once it has committed its changes to its parent object store, the editing context posts the EditingContextDidSaveChangesNotification (EOEditingContextDidSaveChangesNotification in Objective-C).
Customizing Framework Behavior
You can customize the behavior a save operation by assigning delegates to EOEditingContext and EOAdaptorChannel, and implementing the any of the following delegate methods.You can also register to receive the notifications listed below
Locking and Update Strategies
An update operation includes the following ingredients:
- An enterprise object whose data values have been changed
- A means of identifying the row in the database that corresponds to the object
- A strategy for handling update conflicts-either by preventing them from occurring, or by detecting and handling them when they do occur.
The "means of identifying" a row is the primary key or global ID.
An update strategy determines how updates should be made in the face of changes by others. For example, one strategy is to lock a row when it is read so that no one else can change it until you're done with it; this is called pessimistic locking. Another strategy is to compare the state of a row as you fetched it-that is, the row's snapshot-with the database row at update time to confirm that the database row hasn't been changed by someone else. This is called optimistic locking, because it assumes a conflicting update won't occur, but does check at the last minute. You can set your update strategy using the EODatabaseContext method setUpdateStrategy (setUpdateStrategy: in Objective-C). Optimistic locking is the default.
Enterprise Objects Framework also supports "on-demand" locking, in which specific optimistic locks can be promoted to database locks during the course of program execution. In other words, you can lock single objects. There are three ways to use on-demand locking. Use the EODatabaseContext method lockObjectWithGlobalID (lockObjectWithGlobalID:editingContext: in Objective-C) to lock a database row for a particular object. Use the EODatabaseContext method objectsWithFetchSpecification (objectsWithFetchSpecification:editingContext: in Objective-C) with a fetch specification that's configured to lock rows as they're fetched. Or use the EOEditingContext method lockObject (lockObject: in Objective-C).
Handling Conflicts
The locking approach you use determines at what point conflicts are detected and how you can handle them.
- Pessimistic locking
- Optimistic locking
- On-demand locking
When you use pessimistic locking, conflicts are detected as soon as you fetch a row. This is because when you fetch a row with pessimistic locking, you attempt to put a lock on it. If someone else has a lock on the row, the lock (and hence, the fetch operation) is refused. Your application can display a panel at that point telling the user to try again later.
Since pessimistic locking puts a lock on a row when it fetches it, you can generally assume that you won't experience conflicts when you save changes. However, this behavior is ultimately dependent on how the database server handles locks.
When you use optimistic locking, conflicts aren't detected until you attempt to save. At that point, the database row is checked against the snapshot to make sure the row hasn't changed. If the row and the snapshot don't match, the save operation is aborted, the transaction is rolled back, and an exception is thrown. To handle the error you can catch the exception, refresh the conflicted object from the updated database data, and save again.
On-demand locking mixes characteristics of both pessimistic and optimistic locking. With on-demand locking, you've already fetched the object, and you're trying to get a lock on it after the fact. When you try to get a lock on the object's corresponding database row, you can get a failure for one of two reasons: either because the row doesn't match the snapshot (optimistic locking), or because someone else has a lock on the row on the server (pessimistic locking).
When on-demand locking fails for either reason, it throws an exception. To handle the error you can catch the exception, refresh the conflicted object from the updated database data, and try to get a lock on it again.
As with pessimistic locking, because on-demand locking locks the row, you can generally assume that you won't experience conflicts when you save changes. Again, this behavior is ultimately dependent on how the database server handles locks.
Table of Contents Next Section