Table of Contents Previous Section
Modeling Relationships
How you model relationships is perhaps the most complex and interesting part of a database-to-objects mapping. This section describes some of the finer points of relationship modeling.
Modeling Optional To-One Relationships
A to-one relationship is optional if the relationship's destination object can be null (nil in Objective-C). For example, a Member entity's creditCard relationship is optional if a Member object isn't required to have a CreditCard object.Note: Occasionally, a mandatory to-one relationship doesn't resolve to a destination object. For example, suppose your application's Movie database contains legacy data for which relational integrity constraints weren't strictly enforced. As a result, some Movies don't have corresponding Studios even though the Movies-to-Studios relationship is mandatory. The techniques for handling these errant to-one relationships are the same as those for handling optional to-one relationships.
You can model an optional to-one relationship many different ways, depending on how you represent the relationship in the database-as a foreign key to primary key join or as a primary key to primary key join.
Note: For to-one relationships, Enterprise Objects Framework doesn't support primary key to foreign key joins. The destination join attribute in a to-one relationship must be the destination entity's primary key.
Using a foreign key to primary key join, you include the destination row's primary key in the source row. For example, in the relationship shown in Figure 24, the creditCard relationship's source table (MEMBER) has a foreign key (CARD_NUMBER) to the destination table (CREDIT_CARD). Using this approach, you can model an optional
to-one relationship as a true to-one relationship just as you would model a mandatory to-one relationship.
Figure 24. Storing a Foreign Key in the Source Table
Figure 25. Storing a Foreign Key in the Destination Table
The exception to this is when the relationship is based on a foreign key to primary key join and the relationship's source object doesn't have a corresponding destination. Instead of creating a fault, Enterprise Objects Framework assigns null (or nil in Objective-C) to the source object's relationship property. For example, if a Member doesn't have a corresponding CreditCard, the corresponding MEMBER record's CARD_NUMBER value is NULL. When Enterprise Objects Framework sees the null-valued CARD_NUMBER attribute, it sets the Member's creditCard property to null.
- Model the relationship as a mandatory to-one, but allow the destination entity to have null-valued attributes. For example, assume that a relationship between Customers and Addresses is optional. To model the relationship as a mandatory to-one, Customers who don't provide their Addresses have corresponding Address objects with null-valued streetAddress, city, state, and zip attributes. The Customers also have Address rows in the database, but the Address rows contain NULLs in all the columns except the primary key column (whose value matches that of the corresponding Customer).
- Model the relationship as a to-many. This approach is useful when you think that a to-one relationship may evolve into a to-many relationship in the future. For example, current requirements for a Movie application specify that a Talent object may only have one photo. However, the requirements for the next version of the application mention a Talent's portfolio.
- Handle the exception thrown by faults that don't correspond to a destination object. This approach is probably the best for handling optional to-one relationships based on primary key to primary key joins.
- Implement the delegate method databaseContextFailedToFetchObject (databaseContext:failedToFetchObject:globalID: in Objective-C). This approach is best for handling mandatory to-one relationships with errant data (source rows that don't have corresponding destinations).
This approach is a good choice when the destination object contains what are conceptually attributes of the source object. For example, conceptually photo is an attribute of a Talent object. It's implemented using a to-one relationship for performance reasons. (Photo data is very large, and isn't fetched unless-and until-it's needed.)
Use a Mandatory To-One Relationship
This approach is used for the Movies database to model Talent's talentPhoto relationship. Although a Talent object doesn't have to have a photo, it does have to have a corresponding TalentPhoto object. As shown in Figure 26, a Talent object that doesn't have a photo has a TalentPhoto object whose photo attribute is null (nil in Objective-C).
Figure 26. A Destination Object with null-Valued Attributes
Use a To-Many Relationship
To-many relationships use a different faulting mechanism than to-ones. A fault for a to-many relationship replaces itself with an NSArray of corresponding destination objects, and it doesn't throw an exception if it doesn't find any. If you use a to-many relationship to model an optional to-one and no destination object exists, the array is simply empty. If the relationship does have a destination object, it's the first and only object in the array.
public void setTalentPhoto(TalentPhoto talentPhoto)In Objective-C:
{
willChange();
_talentPhotoArray.removeAllObjects();
if (talentPhoto != null)
_talentPhotoArray.addObject(talentPhoto);
}
public TalentPhoto talentPhoto()
{
willRead();
if (_talentPhotoArray.count() > 0)
return _talentPhotoArray.objectAtIndex(0);
return null;
}
- (void)setTalentPhoto:(TalentPhoto *)talentPhoto
{
[self willChange];
[_talentPhotoArray removeAllObjects];
if (_talentPhotoArray)
[_talentPhotoArray addObject:talentPhoto];
}
- (id)talentPhoto
{
if ([_talentPhotoArray count])
return [_talentPhotoArray objectAtIndex:0];
return nil;
}
Handle the Exception
You can use a to-one relationship if you handle any exceptions that are thrown when a fault doesn't resolve to a destination object. For example, in the Talent enterprise object, you would implement the talentPhoto relationship "get" method as follows:In Java:
public TalentPhoto talentPhoto()In Objective-C:
{
try {
// If the receiver is a fault, sending it a willRead
// message attempts to resolve it. If the
// corresponding row doesn't exist in the database,
// an exception is thrown.
talentPhoto.willRead();
} catch (NSException e) {
talentPhoto = null;
}
return talentPhoto;
}
- (TalentPhoto *)talentPhotoSending willRead (or self in Objective-C) to a fault triggers it to fetch its corresponding enterprise object. If a Talent instance doesn't have a corresponding TalentPhoto, sending willRead to the talentPhoto property throws an exception. In the talentPhoto method above, the exception handler simply sets the property to null (first autoreleasing the talentPhoto fault in Objective-C).
{
NS_DURING
// If the receiver is a fault, sending it a self
// message attempts to resolve it. If the
// corresponding row doesn't exist in the database,
// an exception is raised.
[talentPhoto self];
NS_HANDLER
[talentPhoto autorelease];
talentPhoto = nil;
NS_ENDHANDLER
return talentPhoto;
}
Implement databaseContextFailedToFetchObject
With the EODatabaseContext delegate method databaseContextFailedToFetchObject (databaseContext:failedToFetchObject:globalID: in Objective-C), you can prevent an exception from being thrown when a fault doesn't resolve to a destination object. This method is invoked when a fault for a to-one relationship can't find its corresponding object in the database. By returning false (NO in Objective-C), you can prevent the EODatabaseContext from raising an exception.For example, to handle mandatory to-one relationships with errant data (source rows that don't have corresponding destinations), you could implement the delegate method to insert the empty object, thereby supplying the missing destination object:
In Java:
public boolean databaseContextFailedToFetchObject(In Objective-C:
EODatabaseContext context,
Object object,
EOGlobalID gid)
{
// Perform a check to determine whether to intervene
if (...) {
// Set values in your object (if necesssary).
object.editingContext().insertObject(object);
return false;
}
return true;
}
- (BOOL)databaseContext:(EODatabaseContext *)contextIn the above implementations, the delegate method first checks to see if it should intervene. For example, the method might check to see if object is an instance of the TalentPhoto class. If the delegate determines that object represents a destination object that's missing from the database, the delegate queues object for insertion into the database by inserting it into its editing context. It returns false (NO in Objective-C) indicating that the delegate has handled the error and that the EODatabaseContext shouldn't throw an exception.
failedToFetchObject:(id)object
globalID:(EOGlobalID *)gid
{
// Perform a check to determine whether to intervene
if (...) {
// Set values in your object (if necesssary).
[[object editingContext] insertObject:object];
return NO;
}
return YES;
}
Modeling Many-To-Many Relationships
To model a many-to-many relationship between objects is simple: each object manages a collection of the other kind. For example, consider the many-to-many relationship between employees and projects. To model this relationship in objects, an Employee has an NSArray, projects, of all the projects he or she works on; and a Project class has an NSArray, employees, of all its members.To model a many-to-many relationship in a database, you have to create an intermediate table (also known as a correlation or join table). For example, the database for employees and projects might have EMPLOYEE, PROJECT, and EMP_PROJ tables, where EMP_PROJ is the correlation table. The appendix "Entity-Relationship Modeling" provides more information on the tables behind a many-to-many relationship.
Given the relational database representation of a many-to-many, how do you get the object model you want? You don't want to see evidence of the correlation table in your object model, and you don't want to write code to maintain database correlation rows. With Enterprise Objects Framework, you don't have to. Simply use flattened relationships as described in the chapter "Using EOModeler" to hide your correlation tables.
A model with the following features has the effect of hiding the EMP_PROJ correlation table from its object model altogether:
- Employee and Project entities whose to-many relationships to the EmpProj entity (toEmpProj) are not class properties.
- The flattened relationships projects and employees in Employee and Project, respectively, are class properties.
However, what do you do when a correlation table contains extra attributes that are interesting? For example, the MOVIE_ROLE table in the sample Movies database is a correlation table between movies and the actors who star in them. In addition to foreign keys for MOVIE and TALENT, the MOVIE_ROLE table also contains the name of the role the actor plays in the film. In this case, MovieRole enterprise objects actually have a place in the object model even though they're fetched from a correlation table.
If you want to programmatically access both a Movie's roles and its actors directly from the Movie object, you should do the following:
- Create a movieRole relationship from Movie to MovieRole and set it to be a class property.
- Create a talent relationship from MovieRole to Talent.
- Define an actors method in the Movie class that returns the Talent objects by getting them from the corresponding MovieRoles.
Instead, if you create an actors method that traverses the object graph through the Movie's MovieRole objects, you avoid any consistency problems.
For display purposes, you don't even need an accessor method to bypass a correlation object. Instead, you can use key paths. For example, you can use the key path roles.talent to access a Movie's Talent objects in a master-detail configuration between a Movie EODisplayGroup and a Talent EODisplayGroup.
Table of Contents Next Section