views:

32

answers:

2

I am having a hard time wrapping my head around data models. Let me start with showing how I would declare the class if I just had to have in-memory objects.

@interface PlayerState {

  NSSet /* of SavedDie */ *savedDice;
  NSSet /* of SavedDie */ *immediateRolls;

}

@property (nonatomic, retain) NSSet *savedDice;
@property (nonatomic, retain) NSSet *immediateRolls;

@end;

I just have two different groups of the same type of thing. So when I model it in Core Data I thought I'd just have two to-many relationships from PlayerState to SavedDie.

The problem comes when I try to define an inverse relationship. In the first place, I don't really understand why to-many relationships must have an inverse. I understand you are responsible for maintaining consistency if you don't define an inverse, but even if I do that, I seem to have problems.

Anyway, I can define a to-one relationship on SavedDie called owner, though I don't need to access it in my code. I can define the inverse relationship of savedDice as owner. Then if I try to define the inverse relationship of immediateRolls as owner, it unsets it as the inverse relationship of savedDice. And of course I cannot set the inverse relationship of owner as both savedDice and immediateRolls.

+2  A: 

You can't do quite what you're trying to do here. If I understand you correctly, you want a die to have a single owner relationship, but the playerState to have two distinct saved and immediate relationships, each backlinked to owner. CoreData won't allow this.

You should probably re-think your model a bit; perhaps add a flag to your SavedDie object indicating whether it's a 'saved' roll or an 'immediate' roll. Then add dynamic properties to fetch just saved or immediate dice, based off that flag. One relationship each way. Of course, there are other ways to do this, if they fit better.

Ben Gottlieb
+1  A: 

You can't do this directly in Core Data because, unlike your example above, Core Data has to maintain the relationship graph as well.

To understand this, suppose you setup relationships manually using the example code above. Now imagine that you have a SavedDie class that is saved in the savedDice and immediateRolls of PlayerState. If you want each SavedDie instance to refer to the PlayerState, creating a pointer to the owning PlayerState would be easy but how could you tell which PlayerState property it was stored in. More importantly, how could you enforce it such that the same SavedDie object didn't end up in both properties or in two or more separate PlayerState objects?

Core Data was created to manage all this automatically and it uses the entity graph to do so. Entities are not classes. Instead they are an abstract representation primarily of the relationships between objects. The entity graph won't let you have the one entity have two relationships to another entity because it would be impossible to keep track of which instances of the live data objects went with each other.

There are two ways to solve your problem.

(1) Use entity inheritance: You create an Die entity and set it up with all the attributes you want it to have. Then you create two subentites: SavedDie and ImmediateDie. Then you set relationships:

PlayerState.savedDie<-->SavedDie.playerState 
PlayerState.immediateDie<-->>ImmediateDie.playerState

(2) Use fetched relationship: In this case, you have one Die entity and one relationship:

PlayerState.Die<-->>Die.playerState

... but you have an attribute of Die that makes each instances as saved or immediate. Then you create two fetched relationships whose predicates each look for different states of the flag.

(3) Use a link entity: In this technique, you use an intermediate entity to tie the two main entities together in multiple relationships like so:

PlayerState{
    //...some attributes
    savedDice<-->>PlayerToSavedDie.playerState
    immediateDice<-->>PlayerToImmediateDie.playerState
}

ToDie{
    die<-->SaveDie.player
}
PlayerToSavedDie:ToDie{ //...subentity of ToDie
    //... no attributes
    playerState<<-->PlayerState.savedDice
}

PlayerToImmediateDie:ToDie{ //...subentity of ToDie
    //... no attributes
    playerState<<-->PlayerState.savedDice
}

Die{
    player<-->ToDie.die
}

This way you can move a Die instance around relationships just by deleting one link relationship and creating another. Since the Die.player points to ToDie it will accept either PlayerToSavedDie or PlayerToImmediateDie in that relationship.

Which one of these techniques you use depends on the needs of your data model. If the logical Die objects in one relationship never jump to the other, use (1). If the saved or immediate data is an important attribute of the logical Die objects themselves, use (2). If the logical Die objects need to move between relationships and saved or immediate data is not part of the logical Die object, then use (3).

All this might seem a lot of work but you have to remember one import thing: Relationships are just as import as attributes! A model will not work if its relationships do not reflect the real world objects, events or conditions the model simulates. You should expect to spend sometime thinking about and managing relationships.

Core Data makes relationship management a hundred times easier but it still takes some work.

TechZen