How do entities in relationship know what to join on - ios

This is a basic question from someone transitioning from SQL-based databases that I normally sweep under the carpet but would really like to understand. When two entities are joined in a relationship, how does Core Data figure out what attribute to join on? Does it figure it out by matching attribute names, or just how does it know?
I'm asking to understand why following code is not working.
I have one entity, Books with attributes as follows:
aid|authorname
I have another entity, Authors with attributes
bid|bookname|authorid
//note authorid here is spelled differently than in the author entity.
Authors has a Many relationship to Books named book
Books has One relationship to Authors named author.
In the books VC, in the .h file I have
#property (nonatomic,weak) Books * book;
In the .m file I have following code in ViewDidLoad
NSString *authorname = self.book.author.authorname;
//this is supposed to be book-object.author-relationship,authorname-attribute
//however it displays blank
NSLog(#"author name%#",authorname); //displays blank
I have very similar code working elsewhere in the app that works fine (though both relationships are one-to-one) so I think I must be missing something dumb somewhere.
However, it has prompted me to wonder exactly how does core-data figure out which author goes with which books?
Are the attribute names supposed to match up?
Thanks for any insights.

CoreData "knows" that two objects are related if and only if you tell it, which you do by (assuming you have defined the relationship in the data model editor) assigning one object to the relationship property of the other object:
myBook.author = myAuthor;
or equivalently
[myAuthor addBooksObject:myBook];
Once you've done that, CoreData will "know" that those two objects are related (even if you save the data then reload it). This is completely independent of the attributes (such as aid and authorid) that you might think indicate that two objects are related.
If you want Books to be related to the Author with aid equal to the Book's authorid, you have program it that way. So, if for example you sync an Author from your server, with aid = 123, and then sync some Books with authorid = 123, you will need to fetch (unless you already have a reference to it) the Author with aid = 123, and set the relationship with code similar to the above. I suspect it is this step which you have missed, and is causing the blank author name.
If you do this during your sync process, you can thereafter just rely on the relationship, with no need to worry about primary keys, foreign keys, joins, etc. Behind the scenes, CoreData maintains a unique primary key for each entity, and foreign keys for each relationship. You can see these if you directly inspect the underlying SQLite database, and/or if you activate SQLDebug.

Related

Core Data Relationship For Unidirectional One to Many

What is the best practice for creating Unidirectional One to Many Relationships in Core Data?
For example...
Lets take two classic entity examples, "teacher" and "student".
Each student has one teacher, and each teacher has many students.
In CoreData right now you are forced to provide an inverse such that teacher is forced to have a reference to a 'student'. If you don't you get this nice warning that says something along the lines of...
file:///Users/josephastrahan/Documents/VisualStudioProjects/Swift3WorkOrders/WorkOrders/WorkOrders/WorkOrders.xcdatamodeld/WorkOrders.xcdatamodel/: warning: Misconfigured Property: Teacher.student should have an inverse
What if I don't want teacher to have a reference to student?
Some other posts have brought up that I should just allow the inverse anyways but I think this inverse may be causing an issue with one of my projects.
That said let me explain my exact issue.
Lets say that our teacher has a unique attribute int64 called 'id'. Lets say the students also have unique attribute int64 called 'id'.
The int64 is enforced to be unique by adding a constraint on the model for teacher on id. (refer to image below to see how that is done)
Every year there is new students but the teachers stay the same. So I decided that I want to delete all the students without deleting the reference to the teacher. So I set the delete rule to 'nullify' for the relationship for the teacher to student and 'nullify' for the student to teacher.
Now when I create a new student I want to assign one of the existing teachers to that student... (something like student.teacher = teacher object with id of 1 or the same id as before) however!! , because the teacher has the inverse relationship to a student that no longer exists (which in theory should be null) the program crashes!
I know this is the case as I've used print console logs to narrow it down the exact point that it occurs. Also I know this because if I add the delete rule of cascade for student the crash will go away but...then I lose my teacher! which I don't want...
Some things that I think might be the issue:
1.) When I do my testing I do it at the startup of the program which creates a new context everytime. Could it be that because I never deleted teacher it still thinks it refers to a student from a context that no longer exists? (if I'm even saying this right...)
I'm not sure the best solution to acheive what I'm trying to do with Coredata and any advice is much appreciated!
Note:
Forgot to mention I also have the Merge Policy of: NSMergeByPropertyObjectTrumpMergePolicy, which will overwrite the old data with the new. When I'm creating new students I'm creating new teachers also just using the same id which should follow this policy.
You are almost there.
The advice to keep the inverse relationship is a good one. Keep it.
Your issue is likely caused by different contexts. Instead of holding on to a teacher object in memory, you should fetch the teacher (based on the id) in the context in which you intend to use it.
Your nullified students should not have any impact. A to-many relationship is really a Set<Student>. Make sure the set is empty.
NB:
If you want to keep the student in the database (for historical purposes) - it seems from your description that this is the case - you might also consider another scheme: give your students another attribute (such as a year) and use that to filter the student list. You would not have to delete or nullify anything. You could also do some more interesting time-based queries on the data.
Unique Constraints are available with iOS9. Which have helped iOS Developers with adding and updating records in CoreData.
Unique Constraints make sure that records in an Entity are unique by the given fields. But unique constraints along with To-Many relationship leads to a lot of weird issues while resolving conflicts.
e.g. “Dangling reference to an invalid object.”
This post is basically focused to a small problem that may take days to fix.
http://muhammadzahidimran.com/2016/12/08/coredata-unique-constraints-and-to-many-relationship/

Preventing duplicate NSManagedObjects

Consider two entities Author and Book that are in a many-to-many relationship that are imported into my CoreData store from an external database. What I am confused about is, should I create a new NSManagedObject for each author, even if this author is already in the store? How do I even know that two authors with the same name are the same person? I could for instance end up with 10 John Smiths, and 5 of them are the same person, but there is no way to check this when importing the data, right? Suppose I want to do a fetchrequest for one of these John Smiths, I will still get 10 results. He may also appear as J. Smith, or J.A. Smith. But J. Smith could also be Jenny Smith.
Should I just create an NSManagedObject for each author, and not worry about possible duplicates, or are there other ways around this?
How do I even know that two authors with the same name are the same person?
You don't, and that's the core of your problem right there. You need to allow duplicate names, because names are (usually) not unique. Any technical solution to avoiding or removing duplicates based on name is virtually guaranteed to corrupt your data.
It's not clear where your data is coming from, so it's hard to say what the best fix is. If this is user-entered data, let the user edit an existing author to add or remove titles, to prevent a duplicate. Offer the option to merge two entries in case the user accidentally creates a duplicate.
If the data comes from an online service of some kind, you pretty much have to take what they give you. If they have duplicate entries for authors, you can't reliably do anything about it. You could easily find duplicate names, but that doesn't mean they're the same person.
use a fetch or create pattern as explained in the apple CoreData docs
Core Data doesnt have an implicit uniquing algorithm.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
(they call it find or create) ;)
In order to disambiguate people (or authors) you would need either a "unique" attribute, say an author_id which is guaranteed to be unique when an author will be created.
The other approach is to use heuristics to determine if an object has possibly duplicates This second approach sounds more complex, and actually it IS more complex ;)
Unfortunately, Core Data does not support "unique attributes" (unique keys).
Both approaches can be implemented as proper managed object "validations", which get invoked when the context will be saved.
A sophisticated solution would use a separate index maintained per unique attribute and per context. Using Core Data queries as shown in the sample snippets "Implementing Find-or-Create Efficiently" in order to confirm that the "unique constraint" is fulfilled each time the context is saved, will become quite slow for large data sets.
With iOS 9, Apple introduced unique constraints to Core Data. Now you can specify an attribute that has to be unique.

Core Data Model

I'm struggling with creating a suitable Core Data model for my app. I'm hoping someone here can provide some guidance.
I have two entities -- "Goals" and "Items". The Goals entity contains only a goal description, but any goal may have any number of subgoals, and these may extend multiple levels in a tree structure. Subgoals are to be contained within the same entity, so presumably the Goal entity will contain a pointer to "parent" which will be the parent goal of any subgoal.
There will also be an "Items" entity that contains a couple of text fields and a couple of binary items, and must be linked (ideally, by a unique identifier, perhaps objectID) to the particular goal or subgoal the item(s) are related to.
I am totally fumbling with how to set this model up. I know what attributes need to be in each entity, but the relationships, particularly between goals and "subgoals", has me stumped. I don't seem to be able to turn up any good examples of tree structures in Core Data on the Internet, and even the couple of books I have on Core Data don't seem to address it.
Can anyone here help an old SQL programmer get headed the right direction with these relationships in Core Data? Thanks.
Have you tried creating a one-to-many from Goal to itself, and a one-to-one from Goal to Item? The only thing I would worry about here is circular references.
Also, read Relationships and Fetched Properties in the CoreData Programming Guide.
Here is how it is done:
You set up a to-many relationship from Goal to Item in the model editor. Don't use any ids, foreign keys etc. This is old-fashioned database thinking - you can forget about it. Here we are only dealing with an object graph. The database layer is just an implementation detail for persisting the data.
Make two more relationships in entity Goal to itself: a to-one called parent, a to-many called subGoals. Make them the inverse of each other. Simple!
QED is correct, you can create a to many relationship on goal (call it subgoals) as well as a to-one relationship on goal (call it parentGoal) and set them as inverses to each other.
Then create another to many relationship (call it items) on the goal entity, with the inverse being a to one relationship on the item entity (call it goal). Then you're all set. You don't need to link items with a unique id, just add them to the items relationship.
Also note that if you did want to give items a unique id, do not use the objectID. The objectID should only be used as a temporary id as they are not guaranteed to remain the same. In fact they will change if you ever do a Core Data migration.
One way, though not really great, is to create a another entity, say subGoal, and each goal has one subGoal and each object of subGoal has many goal.

How to store many item flags in core data

I am trying to do the following in my iPad app. I have a structure that allows people to create grouped lists which we call "Templates". So The top level CoreOffer(has Title) which can have many groups(has grouptitle/displayorder) which can have many items(has ItemTitle, DisplayOrder). As shown below. This works great, I can create Templates perfectly.
Image link
http://img405.imageshack.us/img405/9145/screenshot20110610at132.png
But once Templates are created people than can use them to map against the Template which I will call an Evaluation. A Template can be used many times. The Evaluation will contain a date(system generated) and which items from this particular Template have been selected.
Example below, people will be able to check particular rows in the screen below, this is then an Evaluation.
Image link
http://img41.imageshack.us/img41/8049/screenshot20110610at133.png
I am struggling to figure out how to create and store this information in the core data model without duplicating the Template. (struggling coming from a SQL background!) In SQL this would involve something like an evaluation table recording each itemid and its selection status.
I expect its quite simple but I just cant get my head around it!
Thanks
The first thing you want to do is clean up the naming in your data model. Remember, you are dealing with unique objects here and not the names of tables, columns, rows, joins etc in SQL. So, you don't need to prefix everything with "Core" (unless you have multiple kinds of Offer, Group and Item entities.)
Names of entities start with uppercase letters, names of attributes and relationships with lower case. All entity names are singular because the modeling of the entity does not depend on how many instances of the entity there will be or what kind of relationships it will have. To-one relationship names should be singular and to-many plural. These conventions make the code easy to read and convey information about the data model without having to see the actual graphic.
So, we could clean up your existing model like:
Offer{
id:string
title:string
groups<-->>Group.offer
}
Group{
title:string
displayOrder:number
offer<<-->Offer.groups
items<-->>Item.group
}
Item{
title:string
displayOrder:number
isSelected:Bool
group<<-->Group.items
}
Now if you read a keypath in code that goes AnOfferObj.groups.items you can instantly tell you are traversing two to-many relationships without knowing anything else about the data model.
I am unclear exactly what you want your "Evaluations" to "copy". You appear to either want them to "copy" the entire graph of any Offer or you want them "copy" a set of Item objects.
In either case, the solution is to create an Evaluation entity that can form a relationship with either Offer or Item.
In the first case it would look like:
Evaluation{
title:string
offer<<-->Offer.evaluations
}
Offer{
id:string
title:string
groups<-->>Group.offer
evaluations<-->>Evaluation.offer
}
... and in the second case:
Evaluation{
title:string
items<<-->>Item.evaluations
}
Item{
title:string
displayOrder:number
isSelected:Bool
group<<-->Group.items
evaluations<<-->>Evaluation.items
}
Note that in neither case are you duplicating or copying anything. You are just creating a reference to an existing group of objects. In the first case, you would find all the related Item objects for a particular Evaluation object by walking a keypath of offer.groups.items. In the second case, you would walk just the keypath of the items relationship of the Evaluation object with items.
Note that how you ultimately display all this in the UI is independent of the data model. Once you have the objects in hand, you can sort or otherwise arrange them as you need to based on the needs of view currently in use.
Some parting advice: Core Data is not SQL. Entities are not tables. Objects are not rows. Attributes are not columns. Relationships are not joins. Core Data is an object graph management system that may or may not persist the object graph and may or may not use SQL far behind the scenes to do so. Trying to think of Core Data in SQL terms will cause you to completely misunderstand Core Data and result in much grief and wasted time.
Basically, forget everything you know about SQL. That knowledge won't help you understand Core Data and will actively impede your understanding of it.

Create NSFetchedResultsController managing entities with many to many relationship

I have an entity called Project and another entity called Employee. Employees work on multiple projects.
Project entity has project name.
Employee entity has First name, last name, departmentid number.
I want the data to show up in section header table like this
Project 1
Dept1
-firstname1, lastname1
-firstname2,lastname2
dept 2
firstname3, lastname3
firstname4,lastname4
Project 2
Dept1
-firstname1,lastname1
How can I do this? I don't have to display department names, but it has to be sorted that way.
I am using Core Data & UITableView. I need to construct NSFetchResultsController for this.
I think the root of your question comes from the fact that a to-many relationship results in an NSSet when you access it from the from object of the relationship (ie: Project->Employees - results in an NSSet of employees). NSSets are, of course, unordered.
And the answer is this:
You'll need to sort your employee NSSets by hand. I suggest you convert the NSSet to an NSMutableArray, and then use -sortUsingBlock: or something along those lines to do it. To keep yourself from having to re-sort it every time you need it, store it as a member variable of your Project class. In doing so, it should be pretty easy to create it lazily, and only re-sort it when the dataset changes, which will be better for performance.
You MIGHT be able to do something with a subquery in Core Data... but I think you might find that'll hit the disk more often than you might like. (Just a guess there) The technique I've suggested above is a bit less magical, a bit more brute force, but it'll work, and you'll know exactly how it behaves forever.
Use fetchedResultsController to get your 'Project' entities and you will be able to access and display the Employee NSSet in your tableview datasource methods via their relationships.

Resources