Core Data one-to-one relationship does not work properly - core-data

// Test listing all words with their sentence
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Word"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:10];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (Word *info in fetchedObjects) {
NSLog(#"Word object: %#", info.word);
Sentence *details = info.relatedToSentence;
NSLog(#"Sentence object: %#", details.sentence);
}
I have two entities called Word and Sentence. In each entity there is an attribute called word and sentence, respectively. The relationship is inverse, not optional, and one-to-one.
I am able to extract records from both entities separately, but somehow I can't fetch related objects through one entity, what did I do wrong?
The code above works, it is printing out value only for word objects and (null) for sentence objects... I am using sqlite as source for this database. I filled the sqlite file which is created by Xcode after modeling up my model.

For your Core Data model the SQLite tables would look like this:
CREATE TABLE ZSENTENCE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZRELATEDTOWORD INTEGER, ZSENTENCE VARCHAR );
CREATE TABLE ZWORD ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZRELATEDTOSENTENCE INTEGER, ZWORD VARCHAR );
where ZRELATEDTOWORD is the primary key (Z_PK) of the related word, and ZRELATEDTOSENTENCE
the primary key of the related sentence. Both have to be filled correctly.
BUT: Please note that modifying the Core Data SQLite file is not recommended and
error-prone. The format is not documented officially and might change in the future.
I still recommend to write a separate command-line tool (which can use the same Core Data
model as your main project) that uses Core Data methods to create and fill the
initial database.

Related

Fetching counts of to-many Core Data relationships with NSExpression

My Core Data model has a Note entity with a to-many relationship to a Link entity, accessed through a links property on Note.
I'm trying to fetch the count of the links property for each note in Core Data. To do so, I followed the example at the link below, but I'm getting an unexpected result.
Chained expressions to perform calculations in Core Data
Here's how I'm configuring the fetch request and related expression descriptions:
// Create the expression description that should return the link count for each note.
NSExpression *linksExpression = [NSExpression expressionForKeyPath:#"links"];
NSExpression *countExpression = [NSExpression expressionForFunction:#"count:" arguments:#[linksExpression]];
NSExpressionDescription *linkCountExpressionDescription = [[NSExpressionDescription alloc] init];
linkCountExpressionDescription.name = #"linkCount";
linkCountExpressionDescription.expression = countExpression;
linkCountExpressionDescription.expressionResultType = NSInteger32AttributeType;
// Execute a fetch request that should return an array of dictionaries, each with a single "linkCount" key.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Note"];
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = #[linkCountExpressionDescription];
NSArray *countsArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
The SQLite store I'm working with contains 1000 notes, so I expect to get an array of 1000 dictionaries, each containing a LinkCount key and the count of its links relationship. Instead, I get a single dictionary that gives me the total count of all links.
What should I do differently to get the link counts for each note?

Core Data Predicate one-to-many and many-to-one problem

I have Client entities and Job entities.
Each job can have one client. The relationship for the jobs of a client (client<-->>job) is called jobOfClient.
Each client can have many jobs. The relationship for client of a job (job<<-->client) is called clientOfJob.
(Of course, these are inverse relationships.)
I have some predicates that are working, but the last one does not. Leaving out some of the fetchedResultsController set up, here are some of the key lines of code for three cases:
Here, I sort through jobs, looking for jobs that aren't related to any client:
NSEntityDescription * entity = [NSEntityDescription entityForName:#"Job" inManagedObjectContext:dataInterface.managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"clientOfJob == nil"];
Here I sort through jobs, looking for jobs of a particular client:
NSEntityDescription * entity = [NSEntityDescription entityForName:#"Job" inManagedObjectContext:dataInterface.managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"clientOfJob == %#", userState.selectedClient];
But this next one doesn't work. Here I sort through clients, looking for the one client associated with a selected job (or return no result if there is no related client, but that's not the case here).
NSEntityDescription * entity = [NSEntityDescription entityForName:#"Client" inManagedObjectContext:dataInterface.managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"jobOfClient == %#", userState.selectedJob];
The error message is Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
There must be something subtle here that I don't understand. Can someone help me with **the info I have given?
jobOfClient will return a collection of objects, which means your predicate is essentially doing:
NSSet *jobs = [thisObject jobOfClient];
return ([jobs isEqual:aJob]);
Obviously, a collection is never equal to a single value, and so CoreData does not recognize the predicate. To get around this, I think you can do:
[NSPredicate predicateWithFormat:#"jobOfClient CONTAINS %#", [userState selectedJob]];
And to make sure you don't run into this again, I would recommend changing the name of this relationship from jobOfClient to just jobs (using the plural form to indicate it's a to-many relationship, and eliminating the OfClient, because it's already on the Client entity). Your other relationships should probably be similarly renamed. clientOfJob => client, etc.

NSFetchedResultsController ERROR: The fetched object at index X has an out of order section name Y. Objects must be sorted by section name'

I'm implementing search bar in iphone app. I have list of Workers, each have attributes: firstName, lastName, company. Sections of table view are set to company attribute.
I did set predicate when searching:
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"firstName contains[cd] %#", searchBar.text];
and i get error:
NSFetchedResultsController ERROR: The fetched object at index 3 has an out of order section name 'company2. Objects must be sorted by section name'
when I have sortDescriptor:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:YES selector:#selector(caseInsensitiveCompare:)];
I did notice that when I change it to
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName"` ascending:YES selector:#selector(caseInsensitiveCompare:)];
now search works correctly with no error. Does initWithKey param must match attribute name in predicate? I don't get it.
From the NSFetchedResultsController docs:
The fetch request must have at least
one sort descriptor. If the controller
generates sections, the first sort
descriptor in the array is used to
group the objects into sections; its
key must either be the same as
sectionNameKeyPath or the relative
ordering using its key must match that
using sectionNameKeyPath.
In your case, you need to use two sorts (in an array.) Element one should be a sort on the company name attribute and second element should sort on the lastName/firstName attribute.

CoreData, NSManagedObject fetch or create if not exists

I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...

Core Data fetches based on properties of 'ordered' relationships

My app has smart folder like functionality: a predicate is setup with a NSPredicateEditor and used to fill the folder with a fetch request.
The entity used in the search has a to-many relationship. The relationship is ordered, in the sense that an index is stored in the destination entity for sorting purposes.
My problem is that I would like to build in a rule based on the last values in the ordered relationship, but I can't figure out how to build a predicate to do this, because the relationship is not an array. Core data doesn't actually know about the order.
I have a readonly property on the class that returns the ordered items, but this doesn't seem to help with the fetch request because the property is not available in the core data store.
The only option I can think of is to de-normalize and store the last items in the relationship ordered in a separate property. Is that the only solution?
Well, assuming I have understood the problem correctly, I'd do it like this. Lets say you've got two entities, TopEntity has a (NSString *)name property and a to-many relationship to MyEntity which has a (NSString *)data property and (NSInteger)order property.
Lets say you want the TopEntity objects which match a given string, and whose MyEntity orders are satisfy a certain condition, then you can do it with two predicates and an NSFetchRequest like so....
NSManagedObjectContext *context = [self managedObjectContext];
// Create some top level entities
TopEntity *aTop = [TopEntity insertInManagedObjectContext:context];
aTop.name = #"This is Your Name";
TopEntity *bTop = [TopEntity insertInManagedObjectContext:context];
bTop.name = #"This aint a Name";
TopEntity *cTop = [TopEntity insertInManagedObjectContext:context];
cTop.name = #"This is My Name";
// Add some data
NSInteger i, len = 30;
for(i=0; i<len; i++) {
// Create a new object
MyEntity *entity = [MyEntity insertInManagedObjectContext:context];
entity.orderValue = i;
entity.data = [NSString stringWithFormat:#"This is some data: %d", i];
if(i < 10) {
[aTop addObjectsObject:entity];
[entity addTopObject:aTop];
} else if (i < 20) {
[bTop addObjectsObject:entity];
[entity addTopObject:bTop];
} else {
[cTop addObjectsObject:entity];
[entity addTopObject:cTop];
}
}
// Save the context
NSError *error = nil;
[context save:&error];
// A predicate to match against the top objects
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name BEGINSWITH %#", #"This is"];
// A predicate to match against the to-many objects
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"ANY objects.order < %d", 5];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"TopEntity" inManagedObjectContext:context]];
[fetch setPredicate:predicate];
NSArray *result = [[context executeFetchRequest:fetch error:&error] filteredArrayUsingPredicate:secondPredicate];
for(TopEntity *entity in result) {
NSLog(#"entity name: %#", entity.name);
}
So, essentially you can just wrap the results of your fetch request with another predicate and use the ANY keyword.
I've got no idea how efficient that is, but it works for this case. Running the above will output "This is Your Name" i.e. it matches the first TopEntity.
I don't think there's a way to limit to n results in a predicate, only at the fetch request level.
Aside from referencing the last n items in a relationship as you mentioned, you might try a boolean attribute "lastN" and flip them on/off when you curate the order of the list (say, during user-initiated sort or drag-and-drop reordering).
Alternatively, you could create a separate fetch request for each searched thing that sorts by your sort key, ordered descending, and is limited (via -setFetchLimit: ) to n results.
Tracking this as a relationship or an attribute is somewhat "messy" whereas the fetch limit is more expensive (because of multiple round trips). If your reordering is done by one-off user actions, it might be better performance-wise to use the relationship or attribute approach since the work is amortized rather than done all at once in a series of fetches. I haven't found a better way myself and will follow this one closely. :-)

Resources