Table of Contents Previous Section
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
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.
- 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 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.
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.
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:).
- 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 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.
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 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.
Note that in Objective-C, the entity class description creates objects and initializes them with the method initWithEditingContext:classDescription:globalID:, if it exists (it uses init otherwise). Your enterprise object class can implement initWithEditingContext:classDescription:globalID: instead of simply init if you need to do anything with the object's editing context, class description, or globalID at this stage in the process.
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. Flow of Data During a Fetch
- 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:
- 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.
- 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.
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".
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".
The EOModel, which is used to convert database data to objects, is also used when a newly allocated enterprise object is initialized. Whereas the dictionary contains an entry for each of the row's columns (those returned by sending attributesToFetch to an EOEntity), the enterprise object initialized from the dictionary only contains the attributes that are defined in the EOModel as class properties.
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.
Also, relationship references are initialized for any relationship properties defined in the EOModel. For example, an Employee object might have a reference to the employee's department, which in database terms represents a join between the EMPLOYEE and DEPARTMENT tables. Class properties that are relationships are represented in the object graph as faults until they're accessed.
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
- Snapshots
- Faults
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.
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.
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
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.
For more information on snapshots, see the EODatabaseContext class specification in the Enterprise Objects Framework Reference.
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.
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
Table of Contents Next Section