We have a pretty robust and fleshed-out system for detecting changes on the models in our iOS app using NSManagedObjectContextObjectsDidChangeNotification. This works well for our attribute changes as well as changes in 1:1 and 1:M relationships.
We're trying to build in some M:M relationships to our schema now and I'm struggling to see how Coredata's notification system will allow us to observe specific changes in M:M relationships.
I'm guessing that if a M:M relationship changed, the two objects involved in the change would both appear in the NSUpdatedObjectsKey list, but there wouldn't be any specific annotation to say that a M:M relationship between them had changed, or which M:M relationship that was (assuming there are possibly more than one M:M relationship between two models).
Has anyone had any luck in using Coredata's notifications system to detect changes in M:M relationships? Or are they basically difficult to work with and people tend to resort to explicitly creating the "joining table" as a real type (and thereby ending up with two 1:M relationships instead of a single M:M relationship)?
You are correct: when a M:M relationship changes, the objects at both ends of the relationship show up in the appropriate dictionaries in the NSManagedObjectContextObjectsDidChangeNotification.
If you're scanning the objects looking for the specific properties that have changed, you can use the method changedValues (or, better, changedValuesForCurrentEvent on ios5) to find out the property names. You are probably already doing this.
You can then inspect the model to see if any given property is many:many or not:
-(void)notifyObjectsChanged:(NSNotification *)note
{
assert([NSManagedObjectContextObjectsDidChangeNotification isEqual:note.name]);
for (NSManagedObject *mo in [note.userInfo objectForKey:NSUpdatedObjectsKey])
{
NSLog(#"for %# :", mo.objectID);
NSDictionary *rels = mo.entity.relationshipsByName;
for (NSString *prop in [mo.changedValuesForCurrentEvent allKeys])
{
NSRelationshipDescription *rel = [rels objectForKey:prop];
if (rel.isToMany && rel.inverseRelationship.isToMany)
NSLog(#"many-to-many relationship %# changed", prop);
}
}
}
(untested, off the top of my head, watch for typos!)
The essential technique is that managed objects keep references to the object model description, which you can inspect at runtime.
Related
I'm new to Core Data and I'm trying to implement it into my existing project. Here is my model:
Now, there's some things that don't make sense to me, likely because I haven't modelled it correctly.
CMAJournal is my top level object with an ordered set of CMAEntry objects and an ordered set of CMAUserDefine objects.
Here's my problem:
Each CMAUserDefine object has an ordered set of objects. For example, the "Baits" CMAUserDefine will have an ordered set of CMABait objects, the "Species" CMAUserDefine will have an ordered set of CMASpecies objects, etc.
Each CMAEntry object has attributes like baitUsed, fishSpecies, etc. that point to an object in the respective CMAUserDefine object. This is so if changes are made, each CMAEntry that references that object is also changed.
Now, from what I've read I should have inverses for each of my relationships. This doesn't make sense in my model. For example, I could have 5 CMAEntry objects whose baitUsed property points to the same CMABait object. Which CMAEntry does the CMABait's entry property point to if there are 5 CMAEntry objects that reference that CMABait? I don't think it should point to anything.
What I want is for all CMAUserDefine objects (i.e. all CMABait, CMASpecies, CMALocation, etc. objects) to be stored in the CMAJournal userDefines set, and have those objects be referenced in each CMAEntry.
I originally had this working great with NSArchiving, but the archive file size was MASSIVE. I mean, 18+ MB for 16 or so entries (which included about 20 images). And from what I've read, Core Data is something I should learn anyway.
So I'm wondering, is my model wrong? Did I take the wrong approach? Is there a more efficient way of using NSArchiver that will better fit my needs?
I hope that makes sense. Please let me know if I need to explain it better.
Thanks!
E: What lead me to this question is getting a bunch of "Dangling reference to an invalid object." = "" errors when trying to save.
A. Some Basics
Core Data needs a inverse relationship to model the relationship. To make a long story short:
In an object graph as modeled by Core Data a reference semantically points from the source object to a destination object. Therefore you use a single reference as CMASpecies's fishSpecies to model a to-one relationship and a collection as NSSet to model a to-many relationship. You do not care about the type of the inverse relationship. In many cases you do not have one at all.
In a relational data base relationships are modeled differently: If you have a 1:N (one-to-many) relationship the relationship is stored on the destination side. The reason for this is, that in a rDB every entity has a fixed size and therefore cannot reference a variable number of destinations. If you have a many-to-many relationship (N:M), a additional table is needed.
As you can see, in an object graph the types of relationships are to-one and to-many only depending on the source, while in rDB the types of relationships are one-to-one, one-to-many, many-to-many depending on both source and destination.
To select the right kind of rDB modeling Core Data wants to know the type of the inverse relationship.
Type Object graph Inverse | rDB
1:1 to-one id to-one id | source or destination attribute
1:N collection to-one id | destination attribute
N:M collection collection | additional table with two attributes
B. To your Q
In your case, if a CMAEntry object refers exactly one CMASpecies object, but a CMASpecies object can be referred by many CMAEntry objects, this simply means that the inverse relationship is a to-many relationship.
Yes, it is strange for a OOP developer to have such inverse relationships. For a SQL developer, it is the usual case. Developing an ORM (object relational mapper) this is one of the problems. (I know that, because I'm doing that for Objective-Cloud right now. But I did if different, more the OOP's point of view.) Every solution is a kind of unusual for one side. Somebody called ORM the "vietnam of software development".
To have a more simple example: Modeling a sports league you will find yourself having a entity Match with the properties homeTeam and guestTeam. You want to have an inverse relationship, no not homeMatches and guestMatches, but simply matches. This is obviously no inverse. Simply add inverse relationship, if Core Data wants and don't care about it.
I'm working on a data-collecting app and I'm having trouble gaining an understanding of how these concepts connect? Here is my scheme:
Site <----->> Station <------->> Observation Event
Site has one attribute, the name of a Site, and can containing multiple stations. Each station will have multiple observations over time. I have these set with the Event to the left as a parent event and created one-to-many relationships as diagrammed, Since each observation event will need to be tagged with site and station.
I'm assuming the parent entity is the best way to create this, or is that what a relationship would do? I expect the user would setup site/station data ahead of time and then observation data would be filled in as they were made.
In short, I just can't wrap my head around what a relationship does in core data and if a parent entity would be redundant. The core data documentation is just not clear to me on this. Any help would be vastly appreciated!!
In essence, what you're going to see when you generate your entity classes, is that in addition to the attributes of each entity you'll have an NSSet for the "to-many" relationship. You can reference any of the "records" in the to-Many relationship by the values in the set.
It seems complicated at first but then it makes total sense. So, if you want to look at the stations, you'll maybe have a "stations" set that includes a list of managed objects for each of the station entities for that site. Each station will contain a set with the managed objects for each of the related observations.
So, once you have a Site entity, you could look at all the stations for that site with something like this:
Site *site = (Site *) managedObjectForSite;
for (NSManagedObject *station in site.stations)
{
Station *stat = (Station *) station;
(do what you need to with the station record)
}
You "link" sites with stations by adding members to the stations set of a given site record, where each member is a station's managed object. You are relieved of the responsibility of "reading" station records -- once you have the members of the set which are loaded with the site, each of those is effectively a managed object for the related stations.
When the light comes on it will all be crystal clear at once. You have to work through it once then you'll pretty much know what's happening in there..
Please also see this as it may help: One-to-Many Relationship: CoreData
In Core Data, relationships have a similar function a foreign keys in a classic relational database setup.
In a database, you would "connect" the Site, Station and Event entities with a foreign key:
Site .id = Station .siteID
Station .id = Event .stationID
In core data this is not necessary. Neither of the two entities needs an extra ID attribute, instead you just define one-to-many relationships.
Site <--->> Station <---->> Event
The advantage: you can access the site from the station, or all the stations from the site with transparent and highly legible dot-notation as you would expect from an object graph. You can even conveniently get the site from an event object, etc.
Site *aStationsSite = station.site;
NSSet *aSitesStations = site.stations;
NSSet *aStationsEvents = station.events;
NSSet *sisterStations = station.site.stations;
Site *siteFromEvent = event.station.site;
Similar or perhaps related to this question.
Say I have an object class "Zoo". It has a to-many relationship to objects of (abstract) type "Animal". As such, an Animal belongs to a Zoo object, and has the property 'zoo' and the Zoo object has the property 'animals'.
However, I am generally more interested in concrete sub-entities, such as Giraffe, or Monkey.
I would like to have a relationship property called "giraffes" and "monkeys", but also the property "animals". I would like to create Giraffe objects and add them to the Zoo.
How would this work? I can't specify in the relationships giraffes and monkeys that its inverse is "zoo" because this would be invalid (in the Core Data Editor).
I am generally working with the subclasses, but I need to always be able to ask them what Zoo they belong to, so need a common interface.
Would appreciate some help and please let me know if there's anything I could clarify.
I think you choose a hard way to get what you want. Create your diagram like below and you can fetch everything you need. When you fetch a specific animal (let's say a monkey) you can always get their relations with other entities.
zoo -->> (to many) animal --> (to one) animalType
Based on your description, I'd consider removing the zoo <-> animal relationship as you don't use it often and it will be a burden to maintain. The relationship would also contain a potentially large number of items so you want finer grained control than the relationship alone allows. And, with other relationships you can specify the deletion semantics.
You like using specific methods so add explicit relationships between the Zoo and each of your animal sub-entities. Specify the deletion rules on each of these (so destroying the Zoo kills all the animals) if required. Each animal will have a link to the zoo and it will be appropriately named.
For the Zoo knowing all of the animals, use a fetch request. You don't use it often and, when you do, you should really be specifying the batch faulting approach.
Aside:
Generally you should use the relationship simply as the data source for a fetch request, specifically so that you can specify the batch faulting approach. Even if you have 50 giraffes, that's probably more than you will use at any one time for display on the UI. If you're doing a data operation then directly using the relationship could be good. But if you're listing the items on the UI you should use a fetch request where the predicate uses the relationship to filter the appropriate objects.
Thanks for all the suggestions. In the end, I have a zoo-animals relationship, and on the Zoo object I wrote a few helpers:
#property (nonatomic, readonly) NSArray *giraffes; // or monkeys
- (NSArray*)giraffes
{
NSArray *giraffes = [Giraffe MR_findByAttribute:#"zoo"
withValue:self
andOrderBy:#"name"
ascending:YES
inContext:self.managedObjectContext];
return giraffes;
}
The baseclass was more important than you think, and knowing the Core Data will cache quite a bit, if I call this fairly frequently the performance will not be a problem.
(I use MagicalRecord if that API call looks a little strange.)
By example:
I have an entity User, and an entity Device.
User have a To Many relationship toward Device, called devices.
Device has the inverse of this relationship called user.
Now I collect, persist a bulk of devices from e.g. a network service, hydrate them into an NSSet, then I bound them to a particular user, so I do:
NSSet *collectedDevices = [API getSomeDevices];
someUser.devices = collectedDevices;
Will Core Data populate the inverse user relationship for each Device for me? Does it observe the setters for relationships?
Background:
I'm aware of the Core Data setters for setting collections, but I want to avoid using them. I'm actually reconstructing Core Data entities from JSON representations with KVC without hardcoded attributes, relationships, just enumerating their entity descriptions, and set matching values.
Yes, Core Data will set the inverse relationships whether you use properties or KVC or the Core Data specific methods such as -setPrimitiveValue:forKey:.
However, when it sets that inverse can be slightly variable. It can set it immediately in some situations and in others it may wait until the end of the run loop to set the inverse. As long as all of the objects being related are created against the same NSManagedObjectContext then the referential integrity will be maintained by Core Data.
There is a problem with core data when a to-many relationship has no inverse. Changes made to the related property do not persist. This is a problem many of us have faced, as it can be found by googling.
This is to ask if some of you found a trick/workaround to achieve persistence, beside the obvious answer or adding an inverse relationship.
Background:
Even if unidirectional relationship are discouraged in the documentation, they are not forbidden. The doc only insists on responsibility incurred when having no inverse.
The reason of not wanting an inverse is outlined in the core-data doc: when you have a large number of items linked to one entity the inverse relationship is loading a large NSSet each time an item is added. Consuming memory, possibly more than allowed for no reason.
Example
In employees/department typical paradigm, if you have a huge number of employees able to belong to several departments, you need a to-many relationship from employee to department. You do not want the inverse because each time an employee is linked to a department, a (very) large NSSet must be loaded, updated and saved. Moreover if the department entity is never deleted, graph integrity is easy to maintain.
Please do not reply that this is a feature of core-data and that inverse relationship is mandatory. This is not stated as such and is more like a bug than a feature. Posting a bug report is not solving the point for current deployed systems.
Edit: The Join entity solution
This edit is to give more light and discussion to Dan Shelly's answer proposal below.
First, to reply to your first, I'm not trying to have a many-to-many but a true unidirectional to-many. The very same page your linked has this text a bit below the one you quoted:
Unidirectional Relationships
It is not strictly necessary to model a relationship in both directions. In some cases it may be useful not to, for example when a to-many relationship may have a very large number of destination objects and you are rarely likely to traverse the relationship (you may want to ensure that you do not unnecessarily fault in a large number of objects at the destination of a relationship). Not modeling a relationship in both directions, however, imposes on you a great number of responsibilities, to ensure the consistency of the object graph, for change tracking, and for undo management.
That said your proposed solution of adding an join entity is a way to go if there is no solution to force core-data to generates and updates it automatically.
IMO, and for my use case, the join entity does not even need to have the relationship to Department. This to-one is useless and may be replaced by a property of the join entity keeping related Department information, like its objectID or other indexed property to reach it.
i.e:
DepartmentEmployee:
Properties: Dept_ix (integer)
Relationships: employee (to-one,nullify)
This is a great question.
ButFirst thing first:It clearly state in the documentation:
"Important: You must define many-to-many relationships in both directions—that is, you must specify two relationships, each being the inverse of the other. You can’t just define a to-many relationship in one direction and try to use it as a many-to-many. If you do, you will end up with referential integrity problems."
Never the less, Lets describe the issue (resulting database)
When defining a to-many relationship, the resulting database does not add an additional table to map the relationship.
It only sets a property on the entity at one end of the to-many relationship equal to the last item that referenced it.
Example:
Model:
Entity: Department
Relationships: NONE
Properties: name (string)
Entity: Employee
Relationships: departments (to-many,no-action)
Properties: name
Resulting Database:
ZDEPARTMENT:
Z_PK
Z_ENT
Z_OPT
Z2DEPARTMENTS (int)
ZNAME
ZEMPLOYEE:
Z_PK
Z_ENT
Z_OPT
ZNAME
This structure will obviously result in data inconsistency.
The solution will be to hold an entity: DepartmentEmployee modeling the to-many relationship in both directions but one of them would be unidirectional (Department -> DepartmentEmployee):
DepartmentEmployee:
Relationships: department (to-one,no-action), employee (to-one,nullify)
and you will have to maintain the table upon deletion of a department object.
Hope this made some sense :)
First a reply for your comment:
IMO, and for my use case, the join entity does not even need to have the relationship to Department. This to-one is useless and may be replaced by a property of the join entity keeping related Department information, like its objectID or other indexed property to reach it.
This is exactly what the department property is doing in the joined relationship.
If you would look at the generated SQLite structure, you will see and additional mapping table between the Employee entity and the Department entity, holding only their int64 ids.
Now, the given example was:
Example
In employees/department typical paradigm, if you have a huge number of employees able to belong to several departments, you need a to-many relationship from employee to department. You do not want the inverse because each time an employee is linked to a department, a (very) large NSSet must be loaded, updated and saved. Moreover if the department entity is never deleted, graph integrity is easy to maintain.
A simple ONE-to-many relationship with no inverse could be easily implemented.
You can look at it as just another property on the object in the 'many' side of the relationship.
This example request a ONE-to-many relationship of the kind:
Employee-->>Department (an Employee may belong to many departments)
The inverse is:
Department-->Employee
Since we must not implement a many-to-many relationships without an inverse, we must implement the to-ONE side of the relationship, just to make sure we comply with the implementation of the framework.
Re-iterating:
By the documentation we know that no many-to-many relationship will NOT persist without an inverse relationship being defined.
==>
Since we like to model the relationship without an inverse we will model it only as the to-ONE part of the coupling (modelling it as a to-many will violate the persistency promised by the framework)
Think of it as useful for defining files in a folder (a file may not belong to more than one folder), or parent child relationship.
==>
We must define the relationship as:
Department-->Employee (Which does not make much sense since a department that can hold only one employee is not really a department is it)
To look at it from another angel (negative proof):
Suppose we would like to go against the framework and define a MANY-to-many relationship with no inverse.
==>
That would mean that we will only implement it in one direction leaving a ... to-many relationship or ... MANY-to relationship
==>
this is the same thing isn't it (a to-many relationship from and entity1 to entity2)
==>
NOW, if we have a ONE-to-many relationship and we choose to not implement the inverse of it, we can choose to implement the to-many part? NO WE CANNOT, this will look as only half of a MANY-to-many relationship
==>
We MUST implement the ONE-to part of it.
For making some more sense, I will show the more logical:
Department-->>Employee
So our implementation for this ONE-to-many relationship would be:
Department<--Employee
This will result in the following SQLite DB structure:
ZDEPARTMENT:
Z_PK
Z_ENT
Z_OPT
ZNAME
ZEMPLOYEE:
Z_PK
Z_ENT
Z_OPT
ZDEPARTMENT (int)
ZNAME
We could now define a fetched property on Department to fetch all the employees belonging to it:
employees predicate: department == $FETCH_SOURCE
You can enforce this relationship in the prepareForDeletion method of Department (not tested):
(You will first set the userInfo dictionary on Department to hold the type of enforcement)
(I left the implementation of the 'Deny' rule to the reader :D )
- (void) prepareForDeletion
{
[super prepareForDeletion];
NSEntityDescription* entity = [self entity];
NSDictionary* dict = [entity userInfo] ;
if ([dict count]) {
[dict enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL *stop) {
NSArray* arr = [self valueForKey:key];
if( [value isEqualToString:#"cascade"]) {
for (NSManagedObject* obj in arr) {
[[self managedObjectContext] deleteObject:obj];
}
} else if ( [value isEqualToString:#"nullify"] ) {
NSArray* arr = [self valueForKey:key];
for (NSManagedObject* obj in arr) {
[obj setValue:nil forKey:#"department"];
}
}
}];
}
}
As I see it, this is all you can do with regard to inverse relationships.
If you still believe you need a many-to-many relationship, please refer to my other answer.
Regards,
Dan.
Have you considered doing away with the relationship entirely and programmatically managing the foreign key on employee?
If you have a UI which sets the property from a list of existing Departments (a pick list, etc.) you can simply take the primary key from that list and assign it as the departmentID property on your Employee.
You should then be able to implement a validateDepartmentID:error method on your Employee object which checks that the given departmentID is valid (i.e. is in a fetched list of departments) and/or is not null so that you maintain referential integrity between the Employee and Department.
When fetching the list of Employees in a Department, you can either use fetched properties or add an instance method to the Department which returns an instance of NSFetchedResultsController containing the Department's employee list.
The only other thing you'd need to do is inject some deletion logic in your Department class (likely on -prepareForDeletion) to update the departmentID on any affected child records. That one depends on your business logic.
The Apple docs on property validation cover -prepareForDeletion and -validateValue:forKey:error if you're not familiar with them.