I'm just getting started with Core Data and am not sure how this works. I basically have a Person entity and an alarm entity. Each person can have many alarms. What I want is to go to a detailViewController of the person object and see their alarms. Because NSSet isn't sorted, I have a method to return the alarms sorted like so:
- (NSArray *)sortedTimes {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Alarm" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *timeDescriptor = [[NSSortDescriptor alloc] initWithKey:#"time" ascending:YES selector:#selector(compare:)];
[request setSortDescriptors:#[timeDescriptor]];
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:request error:&error];
// Can I do this???
//self.person.alarms = [NSSet setWithArray:objects];
// for (NSManagedObject *obj in objects) {
// NSDate *date = [obj valueForKey:#"time"];
// NSLog(#"date: %#", [date description]);
// }
return objects;
}
What I'm wondering is, in the line self.person.alarms = [NSSet setWithArray:objects]; is that ok? I guess I'm not sure as to what actually is happening. My executeFetchRequest returns an array of the objects I want. Can I just go ahead and assign it to the person entity's alarm property? I wasn't sure if there was a relationship from Person->Alarm that I should not be mucking with, or if something like this is perfectly legal. Thanks!
First of all, your fetch request returns all alarms, not only the alarms of self.person. You have to add an predicate to the fetch request:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person = %#", self.person];
[request setPredicate:predicate];
(assuming that person is the inverse relationship from the Alarm entity to the Person entity). But you don't really need a fetch request to get the sorted alarms of a person. A more direct way is
NSArray *objects = [[self.person.alarms allObjects]
sortedArrayUsingDescriptors:#[timeDescriptor]];
Now to your question: The statement
self.person.alarms = [NSSet setWithArray:objects];
just re-assigns the same set of alarms to the person. This effectively does not change anything, because it is the same set. In particular, it does not guarantee that self.person.alarms will now be sorted by time.
Remark: It you want to display a table view with the alarms of a person, you can also use a NSFetchedResultsController (FRC) as table view data source. The advantage of using a FRC is that the table view is automatically updated if objects are inserted, removed or updated.
Have a look at the NSFetchedResultsController and NSFetchedResultsControllerDelegate documentation which contains all the required code templates.
Related
I have an Entity called Devices in CoreData. Devices have following attributes:
Name, model, manufacturer, owner. All attributes are of string type. I am using the following code to fetch values from Entity devices using predicate
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Devices" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
request.fetchLimit = Device_LIMIT;
NSSortDescriptor *sortDisc = [NSSortDescriptor sortDescriptorWithKey:#"model" ascending:YES];
[request setSortDescriptors:#[sortDisc]];
NSError *error;
NSArray *array = [self.managedObjectContext executeFetchRequest:request error:&error];
if (array) {
arrName = [array valueForKey:#"Name"];
}
return arrName;
I am getting the desired result this way.But, my question is how can I directly fetch an array from Device Entity for Name attribute, without adding any if-else or for in condition.
You need to fetch an array from Core Data to get these values-- that's just how Core Data works. You could change your code to use NSDictionaryResultType, but then you'd just get an array of dictionaries instead of an array of managed objects. What you're doing is correct, an there's no short cut to get name values without getting an array and extracting the names from that array.
Do a for-loop to add values to arrName as:
NSMutableArray *arrName = [NSMutableArray new];
for (NSManagedObject *object in array) {
[arrName addObject:object[#"Name"]];
}
A nil array is only returned if the fetch request failed with an error.
Your code should be structured such that if there is no array, you should deal with the error.
EDIT: This is fairly true for most methods that return something and take an error as an argument in cocoa. Usually if nothing is returned, then the error should be checked.
The return value for the method executeFetchRequest is an objects array, so you must use a for-loop to take the name attribute out.
I have simple DB scheme:
I fetch "WPRChatBoardItem" objects collection this way:
- (NSArray*)getAllChatBoardItems
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([WPRChatBoardItem class])];
NSSortDescriptor *sort = [NSSortDescriptor
sortDescriptorWithKey:#"order" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
NSError *error;
NSArray *array = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error)
[WPRLogger printError:error where:DEBUG_PLACE];
return array;
}
When I print one of the objects, I see all three "to-many" properties are in status "relationship fault":
After accessing each of them, I see in log fetch requests but but they all return empty even though there are entries in DB file:
I tried to set setReturnsObjectsAsFaults to NO, but this did not help.
I use only one context
There are entries in the table, but they have ZCHATSBOARD = NULL, ie they are not related to any WPRChatBoardItem. There may be a problem with the code where you set the relationship.
my program has a sqlite database with two related tables. One called "Rank" and other one called "Requirement"
I want to fetch all rows from the "Requirement" table that has a relationship with the specific row in a "Rank" table. Following is my code, it grabs the whole table, but I get the specified rows only according to the above mentioned rule.
-(NSArray *) getAllRequirementsForTheRank:(Rank *) rank
{
NSError *error;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Requirement" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate *searchType = [NSPredicate predicateWithFormat:#"Rank = %#", rank];
[fetchRequest setPredicate:searchType];
NSArray *scoutRequirementArray = [self.context executeFetchRequest:fetchRequest error:&error];
for (Requirement *r in scoutRequirementArray)
{
NSLog(#"Requirementttt : %# :", r.requirementName);
}
return scoutRequirementArray;
}
If you have the relationship modelled in core data, just get the linked objects from the relationship property. You don't need another fetch request. rank.requirements will give you an NSSet of everything you need. (I'm assuming names for your object and properties here).
I m trying to update some records in Core Data. I m adopting following steps to get it done
Fetch function with predicate retrieves the records from the Core Data
Store the result set in a Object Array
Loops through the array and update each record
Call save context
I m running into two problems
After Initial run i get < fault > in the log
I m not sure whether the save context will actually save the object
My code:
- (void)fetchExpenses {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"publishTimestamp == nil"];
[request setPredicate:predicate];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setExpenseArray: mutableFetchResults];
[mutableFetchResults release];
[request release];
}
- (void) save: {
[self fetchExpenses];
int i = 1;
int max = [expenseArray count];
for(i=1; i<=max; i++) {
// Get the expense selected.
Expense *expense = [expenseArray objectAtIndex: i];
// Do your updates here
[expense setTimestamp:2]
}
}
The fault you are seeing in the log doesn't indicate an error but means that the managed object is not fully loaded into memory but is instead represented by a fault object. This is normal behavior. When you try to access or change an object attribute the full object will be "faulted" or read-in to memory. It's a confusing old-fashion database terminology that dates back to 1960s.
Your code does not save any objects. Changes to managed objects in memory will not be persisted until you call a save on the managed object context.
You also do not want to use a mutable copy like this:
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
… because it waste memory and can lead to duplicate managed objects. There was some code in Apple docs that got this started but its erroneous. Instead, just use:
NSArray *fetchResults=[managedObjectContext executeFetchRequest:request error:&error];
… which will return an autoreleased array of the managed objects matching the fetch.
So I'm adding a ListItem into ListName(There is a one to many relationship setted up) in a Class A
ListItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:#"ListItem"
inManagedObjectContext:self.context];
//setting some attributes...
[listName addListItemsObject:newItem];
[self.context save:&error];
After that Class B is via a delegate methode called
There I want to get the data out of Core Data, BUT...If I'm fetching all ListName, the ListItems are not up to date(for example only 5 items instead of 6). If I fetch all ListItems then there are all there(6 out of 6).
What is wrong with my code...I need to get all ListNames though
NSError *error;
NSFetchRequest *req = [[NSFetchRequest alloc] init];
if(context == nil)
NSLog(#"context is nil");
NSEntityDescription *descr = [NSEntityDescription entityForName:#"ListName" inManagedObjectContext:self.context];
[req setEntity:descr];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:#"lastModified" ascending:NO];
[req setSortDescriptors:[NSArray arrayWithObject:sort]];
NSArray * results = [self.context executeFetchRequest:req error:&error];
self.listNames = [results mutableCopy];
if ([results count] > 0) {
ListName *test = [results objectAtIndex:0];
[test.listItems count];
NSLog(#"item count on list %i", [test.listItems count]);
//wrong result
NSFetchRequest *newReq = [[NSFetchRequest alloc] init];
NSEntityDescription *descr = [NSEntityDescription entityForName:#"ListItem" inManagedObjectContext:self.context];
[newReq setEntity:descr];
NSArray * results2 = [self.context executeFetchRequest:newReq error:&error];
NSLog(#"item count on items %i", [results2 count]);
//right result
}
Given your data model and code, there is no reason that the count of ListItems in both places as to be the same because the counts are of two different sets of objects that do not necessarily overlap.
The first count is given by this code:
ListName *test = [results objectAtIndex:0];
[test.listItems count];
… which returns the count of ListItems objects in the relationship of a single, particular and unique ListName object. You may have one ListName object or you might have hundreds each of which could have an arbitrary number of related ListItems objects. This code will only count those related to he first ListName object returned.
The second count is given by:
NSFetchRequest *newReq = [[NSFetchRequest alloc] init];
NSEntityDescription *descr = [NSEntityDescription entityForName:#"ListItem" inManagedObjectContext:self.context];
[newReq setEntity:descr];
NSArray * results2 = [self.context executeFetchRequest:newReq error:&error];
NSLog(#"item count on items %i", [results2 count]);
… which returns an unfiltered array containing every instance of ListItems in the persistent store regardless of what relationships they have.
There is no particular reason to expect the first count to ever agree with the second because it will only do so when (1) you have a single ListNames object in the store and (2) every existing ListItems object is in that ListNames.listNames relationship.
Make sure not confuse entities and managed objects.
BTW, you should almost always use reciprocal relationships e.g. if you have ListNames.listItems you should have a reciprocal ListItems.listName.
A simple reset has helped