

Fetching Objects
This section describes the sequence of events that occurs when objects are fetched from the database. It's broken down into the following major sections: Figure 47 provides a high-level view of what happens in Enterprise Objects Framework when you fetch an object.Figure 47. What Happens During a Fetch
Note: Figure 47 is intended to show the flow of data, not the Enterprise Objects Framework architecture as such. For a graphical depiction of the Enterprise Objects Framework architecture, see the chapter "What Is Enterprise Objects Framework?"
The steps illustrated in Figure 47 are described in detail in the following sections.
EODisplayGroup Receives a fetch Message
A fetch most often begins with an EODisplayGroup receiving a fetch message. If you've configured the display group to "fetch on load," the fetch occurs at the end of display group initialization.When a display group receives a fetch message, the following sequence of events occurs:
- The display group sends its EODataSource a fetchObjects message, which for the EODatabaseDataSource subclass results in a fetch against a specific EOEntity.
- The EODatabaseDataSource constructs an EOFetchSpecification for the database data source's EOEntity (if a fetch specification doesn't already exist) and passes the specification to its EOEditingContext in an objectsWithFetchSpecification message (objectsWithFetchSpecification: in Objective-C).
The database data source would already have a fetch specification if you assigned one programmatically or if you assigned one in Interface Builder (or WebObjects Builder in a WebObjects application). Fetch specifications are handled differently depending on how they're configured. This discussion assumes that the fetch specification is a "regular" fetch specification-one that isn't configured to fetch raw rows, to use a custom SQL statement, or to use a stored procedure.
- The receiving editing context forwards this request, in the form of a objectsWithFetchSpecification message (objectsWithFetchSpecification:editingContext: in Objective-C), to its parent object store, eventually reaching the root object store (which is usually an EOObjectStoreCoordinator). An EOObjectStoreCoordinator manages one or more EODatabaseContexts or other EOCooperatingObjectStores.
- The EOObjectStoreCoordinator determines which of its EOCooperatingObjectStores should service the fetch specification and forwards the EOCooperatingObjectStore an objectsWithFetchSpecification message (objectsWithFetchSpecification:editingContext: in Objective-C) to ask it to actually retrieve data from the database.
Inside EODatabaseContext
When an EODatabaseContext receives a fetch request in the form of an objectsWithFetchSpecification message (objectsWithFetchSpecification:editingContext: in Objective-C), it fetches a number of rows from the database, transforms them into enterprise objects, and registers them as needed with the EOEditingContext that received the initial objectsWithFetchSpecification message.To do this it uses an EODatabaseChannel, whose job is specifically to fetch enterprise objects. The database channel in turn uses an EOAdaptorChannel for low-level communication with the database, along with whatever model objects-EOEntities, EOAttributes, and EORelationships-are needed to perform the fetch.
Fetching objects happens in two major steps:
- The database context uses the database channel to select the rows in the database for which objects are being fetched. To do this, it uses the EODatabaseChannel selectObjectsWithFetchSpecification method (selectObjectsWithFetchSpecification:editingContext: in Objective-C), passing in the fetch specification. Minimally, the fetch specification identifies the EOEntity for the objects, which in turn specifies the enterprise object class to instantiate for every object fetched. If you provided the fetch specification that's used, it can also contain a qualifier that restricts the objects to fetch to those that meet specified criteria and a sort ordering with which to sort the objects. The section "Inside EODatabaseChannel"" describes in detail how the fetch specification is handled.
- The database channel fetches each enterprise object, one at a time, as the database context repeatedly sends it the message fetchObject. This method uses state built up in the select step to get data for the object, create an instance if necessary, and register the new object with the editing context.
Customizing Framework Behavior
At this stage in the fetching process, there are several ways you can customize the Framework's default behavior. To get fine-grained control over a database context, you can assign a delegate to it and implement any of the following methods.With respect to locking, note that in addition to setting an overall locking strategy, you can take advantage of EODatabaseContext's "on demand" locking feature to lock individual rows. For more information, see "Locking and Update Strategies".
An EODatabaseContext also posts notifications that your objects can receive and react to.
Inside EODatabaseChannel
In fetchObject, an EODatabaseChannel performs several tasks:- Before anything else can be done, the EODatabaseChannel must read some data from the database. It does so by having its EOAdaptorChannel retrieve a record for the EOEntity being fetched, including the primary key, class properties, attributes used for locking, and any foreign keys used by EORelationships.
- The first thing the database channel does with the fetched record is to get an EOGlobalID for it from the EOEntity by invoking globalIDForRow (globalIDForRow: in Objective-C).
- The EODatabaseChannel records a snapshot for the fetched row. This step can be fairly complicated, as there may already be a snapshot recorded under the globalID. If there isn't, the EODatabase object is simply sent a recordSnapshotForGlobalID message (recordSnapshot:forGlobalID: in Objective-C). If there is a snapshot, however, a decision must be made on how to update the recorded snapshot. You can use the EODatabaseContext delegate method databaseContextShouldUpdateCurrentSnapshot to intervene at this point (in Objective-C, databaseContext: shouldUpdateCurrentSnapshot:newSnapshot:globalID: databaseChannel:).
If the fetch specification is set to refresh refetched objects, an ObjectsChangedInStoreNotification (EOObjectsChangedInStoreNotification in Objective-C) is posted to invalidate (refault) any existing instances corresponding to this globalID.
- The database channel records whether the object was locked when it was selected.
- The database channel then checks with the editing context, using objectForGlobalID (objectForGlobalID: in Objective-C), to see whether a copy of the object already exists in that context.
- If the editing context already has an enterprise object for the global ID-and if it isn't a fault-then it's simply returned; otherwise it returns null (nil in Objective-C).
- If the editing context has no object or fault for the globalID, the database channel invokes the EOEntityClassDescription method createInstanceWithEditingContext (createInstanceWithEditingContext:globalID:zone: in Objective-C).This method finds out what the object's class should be from the EOEntity and creates an object of that class.
The createInstanceWithEditingContext method provides the enterprise object class's constructor with an editing context, the entity class description, and a globalID. You'll rarely need to use this information at this point, but you might choose to if, for example, you need to extract the primary key from the globalID to do some processing on it.
- The database channel invokes the editing context's recordObject method (recordObject:globalID: in Objective-C), in which the newly created object gets uniqued.
- If the editing context has a fault for the globalID, the fault is cleared and initialization proceeds just as if an empty enterprise object had been created and registered.
- To initialize the object, database channel sends the editing context an initializeObject message (initializeObject:withGlobalID:editingContext: in Objective-C), which is passed down the object store hierarchy. If the editing context is nested, it passes the message to its parent EOEditingContext. If the parent EOEditingContext has an object with a matching globalID, that object is used to initialize the child object. Otherwise, the message is passed down to the EODatabaseContext, which initializes the new instance from the appropriate snapshot and creates faults for its relationships. The EODatabaseContext's initializeObject method sets the object's properties using the key-value coding method takeStoredValueForKey (takeStoredValue:forKey: in Objective-C).
- The database channel sends the enterprise object an awakeFromFetch message (awakeFromFetchInEditingContext: in Objective-C). Custom enterprise object classes can override this method to perform additional initialization after an object has been created from a database row and initialized from database values.
Your custom enterprise object classes can also implement the method awakeFromInsertion (awakeFromInsertionInEditingContext: in Objective-C), which is invoked immediately after your application creates a new object and inserts it into an EOEditingContext. This method lets you assign values to newly created enterprise objects. For more discussion of this topic, see the chapter "Designing Enterprise Objects".
Customizing Framework Behavior
At this stage in the fetching process, you can intervene during step 5 above by assigning a delegate to the EODatabaseContext and implementing any of the following methods.Flow of Data During a Fetch
The preceding sections describe what happens in Enterprise Objects Framework when you fetch objects from the database. This section describes the flow of data that occurs during a fetch.Figure 48 shows how a new object gets instantiated with database data. The scenario it depicts is fetching a single, new object from the database.
Figure 48. Flow of Data During a Fetch
This process is described in greater detail below.
The following sequence of events occurs when an object is fetched from the database:
- A database row is fetched as raw binary data.
- The values retrieved from the database are converted from their database-specific types to instances of standard value classes:
NULL values in the database are mapped to instances of EONullValue (EONull in Objective-C).
Additionally, you can map external data types to custom value classes defined by your application. For more discussion of this subject, see the chapter "Advanced Enterprise Object Modeling".
- Once the data has been converted to objects, these objects are put in an NSDictionary. The elements of the dictionary correspond to columns (attributes) in the database table: Their names are the names of the attributes as used by the client application, and their values are the values in the database. The EOModel is used to determine the mapping from external (database) data types to internal (Objective-C) types.
The dictionary provides a snapshot of the database row, and it's later used to initialize the enterprise object. The snapshot also comes into play when changes to the object are saved to the database; for more discussion of this topic see the section "Snapshots".
- A new enterprise object is allocated by EOEntityClassDescription as an object of the Employee class, as determined from the EOModel.
- The enterprise object is initialized from a row snapshot, using the EOModel. Only objects that are class properties are included.
When an enterprise object is initialized, EONullValue objects (or EONull objects in Objective-C) are passed to the object as null (nil in Objective-C) so you don't have to write code to handle NULLs.
Uniquing, Snapshots, and Faults
When you fetch objects in an Enterprise Objects Framework application, the Framework has mechanisms for ensuring that the integrity of the fetched data is maintained. To this end, the Framework implements these features:- Uniquing
Enterprise Objects Framework maintains the mapping of each enterprise object to its corresponding database row, and uses this information to ensure that your object graph does not have two (possibly inconsistent) objects for the same database row.
- Snapshots
When objects are fetched, Enterprise Objects Framework records the state of the corresponding database row. This information is used when changes are saved back out to the database to ensure that the row data has not been changed by someone else since it was last fetched. The information is also used to only update attributes that have changed, rather than all of them.
- Faults
The objects at the destination of a fetched object's relationships are only fetched on demand; however, these objects are represented in your application by stand-in objects called faults to make retrieval of the actual objects easier.
Uniquing
In marrying relational databases to object-oriented programming, one of the key requirements is that a row in the database be associated with only one enterprise object in a given context in your application. Uniquing of enterprise objects limits memory usage and allows you to know with confidence that the object you're interacting with represents the true state of its associated row as it was last fetched into the object graph.Without uniquing, you'd get a new enterprise object every time you fetch its corresponding row, whether explicitly or through resolution of relationships. This is illustrated in Figure 49.
Figure 49. Uniquing of Enterprise Objects
Uniquing occurs in the control layer, and it's based on an object's globalID. A globalID consists of an object's primary key and its associated entity. When a row is fetched to create an object in a particular EOEditingContext, its globalID is checked against the objects already in the EOEditingContext. If a match is found, the newly fetched object isn't added to the context.
A single enterprise object instance exists in one and only one EOEditingContext, but multiple copies of an object can exist in different editing contexts. In other words, object uniquing is scoped to a particular editing context.
Snapshots
When an EODatabaseContext fetches objects from the database, it asks its EODatabase to record a snapshot of the state of the corresponding database row.A snapshot is a dictionary object recording a row's primary key, class properties, foreign keys for class property relationships, and the attributes of that object that are used for locking during an update. (Primary keys and attributes used for locking are defined in a model; see the book Enterprise Objects Framework Tools and Techniques.) A snapshot is recorded under the globalID of its enterprise object whenever the object is fetched or modified.
When changes to an object are saved to the database, the snapshot is compared with the corresponding database row to ensure that the row data hasn't changed since the object was last fetched. For a discussion of how this relates to the update strategy you set for your application, see the section "Locking and Update Strategies".
Faults
One of the most powerful and useful features of the Framework's database level is that it automatically resolves the relationships defined in a model. It does so by delaying the actual retrieval of data-and communication with the database-until the data is needed. This delayed resolution of relationships occurs in two stages: the creation of a placeholder object for the data to be fetched, and the fetching of that data only when it's needed.When the database level fetches an object, it examines the relationships defined in the model and creates objects representing the destinations of the fetched object's relationships. For example, if you fetch an employee object, you can ask for its manager and immediately receive an object; you don't have to get the manager's employee ID from the object you just fetched and fetch the manager yourself.
The database level doesn't immediately fetch data for the destination objects of relationships, however. Fetching is fairly expensive, and further, if the database level fetched objects related to the one explicitly asked for, it would also have to fetch the objects related to those, and so on, until all of the interrelated rows in the database had been retrieved. To avoid this waste of time and resources, the destination objects created are stand-ins, or faults.
Faults come in two varieties: single-object faults for to-one relationships, and array faults for to-many relationships. In Java, a single-object fault is merely a partially initialized enterprise object. It's been created with a constructor of the form:
public MyCustomEO (and so is already associated with a particular editing context, a class description, and a globalID. However, the object's data hasn't yet been fetched from the database. This part of the object's initialization is delayed until the object receives a message which requires it to fetch its data.
EOEditingContext anEOEditingContext,
EOClassDescription anEOClassDescription,
EOGlobalID anEOGlobalID)
In Objective-C on the other hand, single-object faults are objects of a special class (EOFault) whose instances transform themselves into actual enterprise objects-and fetch their data-the first time they're accessed. These Objective-C faults occupies the same amount of memory as an instance of the target class (into which it's eventually transformed), and stores the information needed to retrieve the data associated with the fault (the source globalID and relationship name). A fault object thus consumes about as much memory as an empty instance of its target class.
An Objective-C fault behaves in every way possible as an instance of its target class until it receives a message it can't cover for. For example, if you fetch an Employee object and ask for its manager, you get a fault object representing another Employee object. If you send a class message to this fault object, it returns the Employee class. If you send the fault object a message requesting the value of an attribute, such as lastName, however, it uses the EODatabaseContext that created it to retrieve its data from the database, overwrites its class identity, and invokes the target class's implementation of lastName.
Figure 50 illustrates this process.
Figure 50. Resolution of a Fault Object
Array faults are treated similarly by both languages. They behave as instances of the NSMutableArray class, and are triggered to fetch their objects by any request for a member object or for the number of objects in the array (the number of objects for a to-many relationship can't be determined without actually fetching them all).
For more information on faults, see the EOFaulting interface specification (Java only), the EOFault class specification (Objective-C only), and the EOFaultHandler class specification (or both languages) in the Enterprise Objects Framework Reference.
When an EODatabaseChannel constructs a fault for a to-one relationship, it checks the globalID for the destination to see whether that object already exists in the EOEditingContext. If so, it simply uses that object to immediately resolve the relationship. This preserves the uniqueness requirement for enterprise objects, in that there's never more than one globalID representing the same row in the database. Whether that globalID represents an actual enterprise object or a fault doesn't matter, since the data will be fetched when it's needed.
Similarly, if an EODatabaseChannel fetches data for an object that's already been created as a fault, the EODatabaseChannel fires the fault. In Java, this simply means that it finishes initializing the object with the data it's fetched. In Objective-C, this means that the database channel turns the fault into an instance of its target class, without changing its id, and then initializes the resulting enterprise object. In either case, the process is essentially the same whether you fetch the fault's data or whether the fault fetches the data itself upon being sent a message.
Table of Contents
Next Section