Working With the Object Graph

One of the primary benefits of using Enterprise Objects is that it insulates you from database details. Once you define the mapping between the database and enterprise objects in a model, you generally do not need to think about issues such as key propagation and generation, how object deletions are handled, how operations in the object graph are reflected in the database, and so forth. The frameworks let you focus on object-oriented programming rather than on database programming.

You tell the frameworks what you want to do by performing operations on the object graph. So, it’s important that you understand a bit about how the object graph works and how to manage it. The object graph represents an internally consistent view of an application’s data. The operations you perform on it can affect its consistency, so knowing how to correctly work with it helps you build better applications that take full advantage of the frameworks.

This chapter discusses how to work with and manage the object graph. It tells you what you need to do in an application after data is fetched and before it is saved.

It is divided into the following sections:

Objects Involved in Managing the Object Graph

There are many objects involved in managing the object graph in an Enterprise Objects application, but you most commonly work with these two:

EOEditingContext

An editing context is the most important object in the object graph. Enterprise object instances always live within an editing context. Most of the operations you perform on the object graph you perform from the perspective of an editing context.

NSUndoManager

An undo manager provides a flexible mechanism to undo and redo changes to enterprise objects in the object graph. It provides a convenience both to your application’s users and to you the developer. You usually don’t need to think about the undo manager, but you may encounter resource issues in your applications that require you to customize its behavior.

Getting Information About Changed Objects

An EOEditingContext maintains information about three different kinds of changes to objects in its object graph: insertions, deletions, and updates. After changes have been made to enterprise objects in an editing context and before the changes are committed to the database, you can determine which objects have changed using the methods insertedObjects, deletedObjects, and updatedObjects. These methods return an array of the objects that have been inserted, deleted, and updated, respectively. Before invoking these methods, you should check to see if any of the objects in an editing context have changed by invoking the method hasChanges on the editing context.

Undoing Changes

Once you get information about the changes made to enterprise objects in an editing context as described in Getting Information About Changed Objects, you may want to undo those changes. By invoking undo on an editing context, the latest set of changes to enterprise objects in that editing context are reversed. You can invoke the method redo to reverse the latest undo operation. You can invoke the method revert to discard all changes in an editing context, including all insertions and deletions. This restores changes to updated objects to their last committed values (their values in the database). An even more severe undo operation is possible using the reset method, which clears all enterprise objects in the editing context.

You may wonder what the scope of an undo operation is. When you invoke undo on an editing context, how much is undone? The scope of an undo in Enterprise Objects is event-based. In a WebObjects application, a single request in the request-response loop constitutes an event. In a Java Client application, a single user event such as a mouse click or menu choice that invokes an operation constitutes an event. The change of a single value in an enterprise object doesn’t constitute an event, unless the change is triggered by an atomic event of either of these types. This undo scope ensures that anytime undo is invoked, the object graph returns to a stable state.

The undo support in EOEditingContext is arbitrarily deep for editing contexts that you create programmatically; you can undo an object repeatedly until you restore it to the state it was in when it was first created or fetched into its editing context. Starting with WebObjects 5.2, the default editing context in WOSession objects limits the number of undo operations to 10.

Even after an editing context’s changes are committed to a database, you can undo a change. To support this feature, the undo manager (an instance of NSUndoManager) can consume a lot of memory.

For example, whenever an object is removed from a relationship, the corresponding editing context creates a snapshot of the modified source object. The snapshot, which references the removed object, is referenced by both the editing context and the undo manager. The editing context releases the reference to the snapshot when the change is saved, but the undo manager doesn’t. The undo manager continues holding the snapshot so it can undo the deletion upon request.

The usage patterns of your applications may cause the undo manager to consume an unreasonable amount of memory. A common design pattern to limit an undo manager’s memory use is to clear an undo manager’s stack when an editing context saves. The most severe consequence of this it that it prevents undo beyond the point of saving. To do this, invoke the method removeAllActions on an editing context’s undo manager after invoking saveChanges. You can also set maximum levels of undo using the method setLevelsOfUndo. Or, if an application or editing context doesn’t require an undo manager, you can set an editing context’s undo manager to null with the method setUndoManager.

Discarding Changes

There are a few ways to discard changes to enterprise objects in an editing context. They include:

There are other ways to discard changes to enterprise objects, including those that refetch from the database. They are described in Discarding Cached Objects.

Discarding Cached Objects

As described in Undoing Changes, you can use the methods undo and revert on an editing context to discard changes made to the enterprise objects registered with that editing context. Invocation of those methods results in the refreshing of the data in the editing context’s enterprise objects from cached data (data that is cached either in the undo manager or in the access layer). This is often the behavior you want, but if you’re concerned about the freshness of data in your application as discussed in Ensuring Fresh Data, you may want to force a refetch from the data source. The methods invalidateAllObjects and invalidateObjectsWithGlobalIDs on EOEditingContext provide this behavior.

The effect of these two methods depends on their use. If you invoke invalidateAllObjects on an EOEditingContext directly, it invokes invalidateObjectsWithGlobalIDs on its parent object store, passing as the globalIDs argument all the global IDs registered in the editing context. This message is propagated down the object store hierarchy to the EOObjectStoreCoordinator.

The object store coordinator determines which of its cooperating object stores can service the request and propagates the message appropriately. The cooperating object store that receives the invalidateAllObjects invocation discards the row-level snapshots for the specified global IDs and sends the notification ObjectsChangedInStoreNotification, which results in refaulting all the enterprise objects in the object graph. The next time those objects are accessed, their data is refetched from the database.

Invoking invalidateAllObjects on an editing context affects the enterprise objects in that editing context and all the enterprise objects in all the other editing contexts that share an object store coordinator, too.

If you invoke invalidateAllObjects directly on the EOObjectStoreCoordinator, it invokes invalidateAllObjects on all of its cooperating object stores, which then discard all of the row-level snapshots in the application and refault every enterprise object in all of the application’s editing contexts. So, if your application uses the default configuration of the Enterprise Objects core stack as described in How It Stacks Up, all the enterprise objects in all the application’s sessions are affected.

Be careful when using invalidateAllObjects and invalidateObjectsWithGlobalIDs: They are rather heavy-weight and can have severe consequences. A more controlled way of refreshing data in an application is described in Refreshing Cached Data.

Refreshing Cached Data

As discussed throughout this book, Enterprise Objects is essentially a big cache of objects. There are mechanisms within Enterprise Objects to preserve the integrity of its objects and mechanisms to improve performance when dealing with potentially large caches of objects. When you fetch data in an Enterprise Objects application, Enterprise Objects prefers to use data that’s already been fetched (cached data) rather than perform a round trip to the database. The degree to which Enterprise Objects prefers cached data is controlled by many things including an editing context’s fetch timestamp. This is discussed in Ensuring Fresh Data.

In the context of managing the object graph, there are scenarios in which you want to refresh the data in the object graph. You can refresh an entire object graph using the methods invalidateAllObjects and invalidateObjectsWithGlobalIDs on EOEditingContext as described in Discarding Cached Objects, but you often want to refresh the data in a specific object or objects.

By default, when you refetch data, Enterprise Objects does not update the data in enterprise object instances with the refetched data from the database. This is the default behavior that helps maintain an internally consistent view of the application’s data. However, this is not always the behavior you want, especially if you are more concerned with ensuring the freshness of data than anything else (which is especially true in read-only applications).

To override the default behavior, invoke the method setRefreshesRefetchedObjects on an EOFetchSpecification. Then, when you refetch data, the data in enterprise object instances is refreshed with the refreshed data from the database. (You can refetch the data for a particular enterprise object by invoking on it the method refreshObject. If you need to refresh a set of enterprise objects, use a fetch specification.)

By default setRefreshesRefetchedObjects refreshes only the objects you are refetching. For example, if you refetch Employee objects, you don’t also refetch the Employees’ departments. However, by invoking setPrefetchingRelationshipKeyPaths on a fetch specification, the refetch is also propagated for all of the fetched object’s relationships that you specify in that invocation.

Working With Objects in Multiple Editing Contexts

It’s common to need access to an enterprise object across multiple editing contexts. A poor approach to this would be for the first editing context to just get a reference to the enterprise object in the second editing context and modify it directly. But this would violate the rule of keeping each editing context’s object graph internally consistent. Instead of modifying the second editing context’s object, the first editing context needs its own copy of the object. It can then modify its copy without affecting the original. When that editing context saves changes, the changes are propagated to the original object.

There are two methods you can use to get a copy of an enterprise object in an editing context from an original object in another editing context. The first method is EOEditingContext.faultForGlobalID(EOGlobalIDgid, EOEditingContexteditingContext), which is invoked on the editing context in which you want the copy of the object to exist. It requires the global ID of the original object, which you can retrieve by invoking globalIDForObject(EOEnterpriseObjectenterpriseObject) on an editing context in an application.

The other method is EOUtilities.localInstanceOfObject(EOEditingContexteditingContext, EOEnterpriseObjectenterpriseObject), which returns a copy of the enterprise object specified by the enterpriseObject argument. You then must manually insert the enterprise object that is returned into an editing context.

Memory Management

You usually do not need to worry about managing memory in an Enterprise Objects application, but this section may be interesting for advanced users.

EOEditingContexts use weak references to the EOEnterpriseObjects registered with them. EOEnterpriseObjects hold a strong reference to the EOEditingContext in which they are registered. These types of references prevent an EOEditingContext from being garbage collected while an EOEnterpriseObject still requires it. There are several exceptions: