iOS Core Data <fault after searching - ios

All, I am having problems with Core Data.
I have a method that queries all data which matches a jobId
- (JobSummary*)summaryForJobId:(NSInteger)jobId {
NSFetchRequest* request = [[NSFetchRequest alloc] initWithEntityName:[JobSummary entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"jobId = %D", jobId];
JobSummary* summary = [[self.context executeFetchRequest:request error:nil] lastObject];
NSLog(#"DB Summary: %#", summary);
[request setReturnsObjectsAsFaults:NO];
return summary;
}
When called and I log out it works perfect, however when I call it from a seperate view controller like so;
JobSummary *retrievedDictionary = [[FSScheduleDatabaseTransaction new] summaryForJobId:jobid];
When I log out retrievedDictionary it spits out this;
<JobSummary: 0x12de24a0> (entity: JobSummary; id: 0xb3c19b0 <x-coredata://7E9F6C6E-B4A0-4450-8905-184C6C8FB60D/JobSummary/p169> ; data: <fault>)
Any help much appreciated!

It is giving you the log correctly. When ever you try to print the ManagedObject you'll only see a fault in logs.
A fault is a placeholder object that represents a managed object that has not yet been fully realized, or a collection object that represents a relationship:
A managed object fault is an instance of the appropriate class, but its persistent variables are not yet initialized.
A relationship fault is a subclass of the collection class that represents the relationship.
So, in short, unless you try to access those properties, CoreData wont fill property values. It will have a fault. Just trying to log the ManagedObject, without accessing its properties previously, will only show fault.

Related

MagicalRecord: one to many relationship

I m not quite sure if it is the right approach
I want to have a one to many relationship between entity A and B, so A could refer to multiple records of B, but B only to one record of A
my question is how to do this with MagicalRecords... i m familiar with the basic fetching and creating of single entities
but have no clue how to fetch, creat, update entities with realtionships
First of all, you need to beware of the 'context' of CoreData. I usually use [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {}]; method, and create entity inside the block.
I will use my case i show above as an example,
To save a "message" to a "conversation" entity,
ChatConversation* chatConversation = [ChatConversation MR_findFirstByAttribute:#"conversationID" withValue:<your value> inContext:localContext];
^first see if there is a entity already been created using the find first by attribute method
ChatMessage* chatMessage = [ChatMessage MR_createInContext:localContext];
[chatMessage set.....];
......
if(!chatConversation){
//You need to create a new chatConversation entity
chatConversation = [ChatConversation MR_createInContext:localContext];
......
}
[chatMessage setChatConversation:chatConversation];
If you want to get all message from a chat conversation, just fetchAll messages with custom predicates
-(NSFetchedResultsController *)fetchedResultsController {
if(!_fetchedResultsController)
_fetchedResultsController = [ChatMessage MR_fetchAllSortedBy:#"createdAt" ascending:TRUE withPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"conversation.conversationID",self.conversationObject.conversationID] groupBy:nil delegate:self];
return _fetchedResultsController;
}
What do you mean by "how to do this with MagicalRecords"? Cause this is not handle by MagicalRecords.
They are all set in the coredata model file.
For instance, for my project, i am implementing the IM feature.
1. One conversation -> many messages
2. One message ->one conversation
this is the design of data model.
feel free to ask me any questions about Coredata + MR
Cheers!

Creating a memory-only NSManagedObject model

I need to create an instance of a NSManagedObject that will not be saved in CoreData and only in memory.
e.g.:
I have the Item and Log NSManagedObject, and they have relations between them.
I want to be able to create a Log instance without any core data properties, and assign its item property to an Item instance.
I know I can create it in a different, memory-persistence, context (or nil context). But then I can't assign the item property, since my Item instance is in the core-data context.
NSEntityDescription *description = [NSEntityDescription entityForName:#"Log" inManagedObjectContext:defaultContext];
Log *log = [[Log alloc] initWithEntity:description insertIntoManagedObjectContext:nil];
log.item = item;
This code throws an exception when ran:
Illegal attempt to establish a relationship 'item' between objects in different contexts
How can I achieve this in another way?
You can create a NSManagedObjectContext with parent context set to your Log's MOC.
Do you need the relationship to have an inverse? If not, you could use the ObjectID for the item objects as an attribute in the Log entity. You would need to convert the ObjectID to its URIRepresentation, and then convert that to a NSString:
NSURL *itemURI = [item.objectID URIRepresentation];
NSString *itemURIstring = [itemURI absoluteString];
log.itemURI = itemURIstring;
(If item has not yet been saved to the database, it will have a temporary ID - you should test for this with item.objectID.isTemporary otherwise the objectID may change). When you want to find the Item object for a given Log object, reverse the process:
NSURL *itemURI = [NSURL URLwithString:log.itemURI];
NSManagedObjectID *itemObjectID = [self.context.persistentStoreCoordinator managedObjectIDForURIRepresentation:itemURI];
Item *item = [self.context objectWithID:itemObjectID];
Pretty cumbersome!
I guess if you need an inverse, you could do the same (i.e. store a URI for the Log object as a string in Item).

Fetch request returns old data

I'm getting outdated results when executing a fetch request upon a NSManagedObjectContextObjectsDidChangeNotification.
As an example, consider directories with documents that can be deleted logically with a boolean attribute (softDeleted). The fetch request should return all directories that have at least one non-deleted document (directoriesWithDocuments).
The initial state is single directory with a single deleted document. directoriesWithDocuments returns an empty array.
The following code restores the document by setting the softDeleted boolean to NO.
[_context.undoManager beginUndoGrouping];
[_context.undoManager setActionName:#"undelete"];
document.softDeleted = #(NO);
NSError *error;
BOOL success = [_context save:&error]; // This triggers the notification
[_context.undoManager endUndoGrouping];
The save triggers a NSManagedObjectContextObjectsDidChangeNotification. I expected directoriesWithDocuments to return the directory, but instead it still returns an empty array.
- (void)objectsDidChangeNotification:(NSNotification*)notification
{
NSArray *objects = [self directoriesWithDocuments]; // Still empty!
}
Yet, if I execute directoriesWithDocuments after saving the context, either right away or in the next run loop, it returns the directory as expected.
This is the code of the fetch request:
- (NSArray*)directoriesWithDocuments
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY documents.softDeleted == NO"];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Directory"];
fetchRequest.predicate = predicate;
fetchRequest.includesPendingChanges = YES; // Just in case
NSError *error = nil;
NSArray *objects = [_context executeFetchRequest:fetchRequest error:&error];
return directories;
}
I suspect that the context has some kind of cache for fetch requests that is not cleared until the notification is handled. Is this how Core Data is expected to behave? Or am I doing something wrong?
Workaround
I'm currently delaying the execution of the fetch request like this:
- (void)objectsDidChangeNotification:(NSNotification*)notification
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// HACK: Give time to Core Data to process pending changes or invalidate caches
NSArray *objects = [self directoriesWithDocuments]; // Returns the directory as expected
}];
}
Experiments
Per #MikePollard's suggestion, I checked the returned value of directoriesWithDocuments in NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification. The results are (in order):
NSManagedObjectContextObjectsDidChangeNotification: empty (wrong)
NSManagedObjectContextWillSaveNotification: empty (wrong)
NSManagedObjectContextDidSaveNotification: 1 directory (correct)
First of all, it looks like you have a boolean attribute called "deleted" defined in your model.
I recall that doing so can be a significant problem because it conflicts (at a KVC level) with NSManagedObject isDeleted. You might want to change that just to make sure it's not the culprit.
Edit
Thanks for replying. I used deleted as a simple example; it's not the
actual attribute I'm using. Will change it to softDeleted to avoid
confusion
I've updated my suggestion below to match the softDeleted in your example.
That said, I think what's at work here boils down to the question of what constitutes a "change" for includesPendingChanges = YES.
Before the context has completed its save, the only 'change' is to Document entities, not Directory entities.
So when the fetch request includes pending changes, there are no Directory entities with any pending changes so you end up with the previous results.
To test that theory, give this a shot:
[_context.undoManager beginUndoGrouping];
[_context.undoManager setActionName:#"delete"];
document.softDeleted = #(NO);
[document.directory willChangeValueForKey:#"documents"] // any attribute will do really
[document.directory didChangeValueForKey:#"documents"]
NSError *error;
BOOL success = [_context save:&error];
[_context.undoManager endUndoGrouping];
What you're doing with the fake will/did changeValueForKey is to "dirty" the associated Directory object. My guess is that it will then be considered "changed" and, as such, included the fetch results.
It strikes me that the predicate is going to be translated into a SQL statement so until the save hits the DB you're not going to get the result you want ... (Not that'd you'd image that from just reading the NSFetchRequest documentation.)
So try doing the fetch as a result of the NSManagedObjectContextDidSaveNotification.
I bet if you turn on -com.apple.CoreData.SQLDebug 1 you'll see the SQL statement.

Core Data Adding object in 1 to many relationship gives error

I have two Entities one called Games and one called Teams. The Games entity has a to one relationship to Teams called teams and the Teams entity has a to many relationship to Games called games. (A team can be in many games but a game can only have 1 team. I am using a separate entity for Opponents)
I am selecting a team by using it's ID. Here is my code for adding a team to the Games entity:
Games *newGame = (Games *) [NSEntityDescription insertNewObjectForEntityForName:#"Games" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *fetchTeams = [[NSFetchRequest alloc] init];
NSEntityDescription *fetchedTeam = [NSEntityDescription entityForName:#"Teams"
inManagedObjectContext:self.managedObjectContext];
[fetchTeams setEntity:fetchedTeam];
NSArray *fetchedTeams = [self.managedObjectContext executeFetchRequest:fetchTeams error:&error];
for (Teams *myTeam in fetchedTeams) {
if (myTeam.teamID == teamid){
newGame.teams = myTeam;
}
}
The error I am getting is: 'NSInvalidArgumentException', reason: 'The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet.'
I don't understand it, newGame.teams is an object of Teams , it is not an NSSet. If I was doing Teams.games it would be an NSSet.
What am I doing wrong?
You've not described what is the data type of variable "teamid" here. Hence, I'm assuming that it must be some primitive type int.
Based on this assumption, you can make the following changes in your code:
if(fetchedTeams!=nil)
{
if(fetchedTeams.count>0)
{
for(Teams *myTeam in fetchedTeams)
{
//check here because coredata stores a number in NSNumber object.
//Hence you've to get the intValue to make a equality check, like below
if(myTeam.teamID.intValue == teamid)
{
//do your stuff here.
//Also check the last part of my answer, I have a question here.
}
}
}
}
In your code you have "newGame.teams = myTeam". What is the data type of "teams" in "newGame" ? is it Teams* or NSSet* ?

Programmatically update duplicate values in coredata?

I am new in Core Data. I want to update duplicate values. For example my table looks like this
id | Name
============
1 | Joseph
2 | Fernandez
3 | Joseph
4 | James
Say that I want to update Joseph corresponding to id 1 and 4 to "myName". When I tried to update this it only updates the 4th row. I can't find any way to do this in any of the documentation. Can anyone suggest me a solution?
One more question, how can I print all name values?
you will have to read over the documentation to know how to update record
http://www.appcoda.com/core-data-tutorial-update-delete/
James,
I'll try to reply to both your questions with sample code.
To update specific objects you need to se up a new NSFetchRequest with a predicate, grab the objects (of type NSManagedObject), update the values you are interested in and save the context.
So, for example:
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"YourEntityName"];
// set the predicate (it's equal to set a WHERE SQL clause) filtering on the name for example
// use camel case notation if possible, so instead of Name use name (for this you have to changes your model, if you don't want to do it use Name)
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"name == %#", #"Joseph"]];
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
// do some error checking here...
for (NSManagedObject resultItem in results) {
// use KVC (for example) to access your object properties
[resultItem setValue:#"myName" forKey:#"name"];
}
// save your context here
// if you don't save, changes are not stored
To print you need to se up a new NSFetchRequest, grab the objects (of type NSManagedObject) and use NSLog.
So for example:
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"YourEntityName"];
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
// do some error checking here...
for (NSManagedObject resultItem in results) {
NSLog(#"%#", [resultItem valueForKey:#"name"]);
}
P.S. The code I provided is quite simple and the predicate I used to specific values check against the name. Since this could be error prone, I would modify the model and using a sort of guid for each objects you need to use (I don't know if id is for that but I would change its name to another one, for example userId). Once done you can check against it.
Hope that helps.
It's as simple as retrieving the NSManagedObject and changing the Name property. You can retrieve the NSManagedObject with a fetch request. Once you changed the property and you want to keep it changed even when you close the application you'll have to do a save on the managedObjectContext.
You'll have to read over the documentation to get up to speed on core data:
http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdBasics.html#//apple_ref/doc/uid/TP40001650-TP1
Edit: just NSLog whatever you want to know, for example log you fetch request results.

Resources