How do I achieve the effect of two to-many relationships to the same entity from one entity?
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 gr开发者_运维知识库oups 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.
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.
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.
精彩评论