iPhone - core data crashing - iphone

I have two entities: products and bundles. Each one has its class. A product can be in multiple bundles.
Entities are defined like this:
PRODUCTS
name, string
number, integer 16
fromBundle = to-many relationship to product
BUNDLE
name, string
number, integer 16
product = to-many relationship to fromBundle
Products were assigned to bundle like this:
// suppose bundle 1 is composed of products 1, 2, 3 and 4.
NSArray *myProd = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:4],
nil];
int bundleNumber = 1;
NSString *bundleName = #"My Bundle";
Bundle *aBundle = nil;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
request.entity = [NSEntityDescription entityForName:#"Bundle" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat: #"(number == %d)", bundleNumber];
NSError *error = nil;
aBundle = [[context executeFetchRequest:request error:&error] lastObject];
// as the bundle does not exist, this will run
if (!error && !aBundle) {
aBundle = [NSEntityDescription insertNewObjectForEntityForName:#"Bundle" inManagedObjectContext:context];
aBundle.string = bundleName;
aBundle.Number = [NSNumber numberWithInt:bundleNumber];
for (NSNumber *umNum in myProd) {
// the product with number = aNum is retrieved... yes it is valid at this point
Product *oneProduct = [ProductWithNumber:umNum inManagedObjectContext:context];
NSMutableSet *mutableSet = [oneProduct mutableSetValueForKey:#"fromBundle"];
[mutableSet addObject:aBundle];
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// everything is fine at this point.
Now I wish to retrieve a list of all products that belong to a specific bundle...
To do that, I am using this method on Bundle class
+ (NSArray *)ProductsInBundle:(Bundle*)aBundle inManagedObjectContext:(NSManagedObjectContext *)context
{
NSArray *all = nil;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
request.entity = [NSEntityDescription entityForName:#"Products" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"(fromBundle == %#)", aBundle];
NSError *error = nil;
all = [context executeFetchRequest:request error:&error]; // crashes here
return all;
}
it crashes on the assigned line on the last method with the message "to-many key not allowed here" when I try to do this
NSArray *allProductsInBundle = [Bundle ProductsInBundle:aBundle inManagedObjectContext:self.managedObjectContext];
aBundle is valid at this point.

I think your predicate is wrong. You don't have a bundle property, but a fromBundle property.
If it is really fromBundle, then your predicate should be:
equest.predicate = [NSPredicate predicateWithFormat:#"(fromBundle == %#)", aBundle];
EDIT:
If you are trying to do operations on to-many relationships then you'll need to use the aggregate functions for the predicate. I think for your case you'll want the IN operation.
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html#//apple_ref/doc/uid/TP40001795-215891

Why are you doing a fetch when you have a relationship? That is heavy and expensive. Just request the products for the bundle via
[aBundle valueForKey:#"product"];
The fetch is unnecessary and forces a disk hit when you probably don't need one. Core Data most likely has the product relationship cached.
Also, when you are assigning a product to a bundle you do not need to get a mutable set. Just set the bundle into the product via:
[product setValue:bundle forKey:#"fromBundle"];
Core Data will manage the other side of the relationship.

Related

Auto-Incremented Object ID in Core Data?

I am working with several NSManagedObject types with several relationships. How can I tell Core Data to automatically populate object IDs for me? I'm looking for something like an index key in SQL, so that no two instances of a given object are allowed to have the same ID.
Edit:
I'd like for all of my "Account" objects to have unique IDs on them. I was just adding one to the `countForFetchRequest, but I realized that when deleting the second to last object and then adding one, the last two objects now have the same IDs.
How can I ensure that a given value has a unique value for all instances of my "Account" NSManagedObject?
EDIT2:
I need to have a separate ID for sorting purposes.
All NSManagedObjects automatically have a unique NSManagedObjectID. There is no notion of a custom auto-incrementing attribute, but it's certainly easy to write one yourself.
The way I resolved this is with Core Data aggregates. I actually end up assigning the ID myself.
Essentially, I query Core Data for all of the entity IDs of my entity and then iterate through them. If I find an ID which is higher than the current temporary one, I make the temporary ID higher one higher than the aggregated one. When I'm done, I automatically have an ID which is higher than the highest one in the list. The only flaw I see with this is if there is a missing ID. (I believe that there is a simple fix for this as well.)
//
// Create a new entity description
//
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:self.managedObjectContext];
//
// Set the fetch request
//
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:entity];
//
// We need to figure out how many
// existing groups there are so that
// we can set the proper ID.
//
// To do so, we use an aggregated request.
//
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"entityID"]];
NSError *error = nil;
NSArray *existingIDs = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error != nil) {
//
// TODO: Handle error.
//
NSLog(#"Error: %#", [error localizedDescription]);
}
NSInteger newID = 0;
for (NSDictionary *dict in existingIDs) {
NSInteger IDToCompare = [[dict valueForKey:#"entityID"] integerValue];
if (IDToCompare >= newID) {
newID = IDToCompare + 1;
}
}
//
// Create the actual entity
//
MyEntity *newEntity = [[MyEntity alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
//
// Set the ID of the new entity
//
[newEntity setEntityID:[NSNumber numberWithInteger:newID]];
//
// ... More Code ...
//
Accroding to your EDIT2 and Edit3, following answer will help you.. Assume your id field as NSNumber having unsignedInt as ID.
1) Fetch all records for corresponding entity.
NSError *error = nil;
NSArray *array = [self fetchAllFileEntity:&error];
2) Find maximum number belonging to that result.
NSNumber *maxValue = nil;
if (array)
maxValue = [array valueForKeyPath:#"#max.uniqueId.unsignedIntegerValue"];
else
maxValue = [NSNumber numberWithUnsignedInteger:0];
3) Assign maxValue+1 to your new entity
entity.uniqueId = [NSNumber numberWithUnsignedInteger:maxValue.unsignedIntegerValue+1];
I have come up with this solution for the said problem, hope it's gonna be helpful for some one.
AppDelegate *appdelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appdelegate managedObjectContext];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *chatHist = [NSEntityDescription
entityForName:#"ChatHistory" inManagedObjectContext:context];
[fetchRequest setEntity:chatHist];
int chatIdNumber = 0;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if ([fetchedObjects count] > 0) {
ChatHistory *chatHistObj = [fetchedObjects objectAtIndex:[fetchedObjects count]-1];
chatIdNumber = [chatHistObj.chatId intValue];
}
chatIdNumber = chatIdNumber+1;
ChatHistory *chat_History = [NSEntityDescription insertNewObjectForEntityForName:#"ChatHistory" inManagedObjectContext:context];
chat_History.chatId = [NSString stringWithFormat:#"%d",chatIdNumber];

iOS CoreData update entity object

I'm implementing a car manager for an iPhone app and I have trouble saving a new car. So I have a "Car" entity (and per-loaded DB), containing multiples attributes. I have 2 booleans "saved" and "selected" to track which car the user added to his list (if added, saved = 1) and which car is currently selected. So when I create a new car, I deselect the old one (selected=0), and want to modify the new car to set its attributes saved=1 and selected=1.
Here is my functions:
- (IBAction)save
{
// Disable the previous car selection.
[self deselectCurrentSelectedCar];
// Add new car as saved and selected.
[self saveAndSelectNewCar];
// Call the delegate to dismiss the modal view.
[_delegate dismissAndSave];
}
- (void)deselectCurrentSelectedCar
{
// Fetched saved car.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selected = 1"];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
}
else {
// Get car and assign new selected value.
Car *carToSave = (Car *)[mutableFetchResults objectAtIndex:0];
[carToSave setSelected:[NSNumber numberWithInt:0]];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: error saving car.");
}
else {
NSLog(#"[AddCarViewController] deselect car: car saved.");
}
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
- (void)saveAndSelectNewCar
{
// Get the car, and pass to the delegate the new settings.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(year=%#) AND (make=%#) AND (model=%#) AND (d=%#) AND (n=%#)", _car.year, _car.make, _car.model, _car.d, _car.n];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle error.
NSLog(#"[AddCarViewController] save and select: can't save the new car");
}
else {
// Get the car selected.
Car *selectedCar = (Car *)[mutableFetchResults objectAtIndex:0];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"selected"];
[selectedCar setValue:[NSNumber numberWithInt:1] forKey:#"saved"];
// Save the car.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"[AddCarViewController] save and select: error saving car.");
}
else {
NSLog(#"[AddCarViewController] save and select: car saved.");
}
// Add car to delegate.
[EcoAppAppDelegate setUserCar:selectedCar];
}
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
And I have this log all the time: "error saving car." on both functions. So there is definitely something wrong.
Also, it's pretty anoying to fetch the car I want to modify it, instead of doing right away an update. If there another please tell me!
Thanks.
In your header file, you should set up a mutable array for your cars.
NSMutableArray *carArray;
and
#property (nonatomic, retain) NSMutableArray *carArray;
Then make sure to synthesize it in your implementation file. Then when you fetch from your managed object context, you can set your array with the contents returned
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Car" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"[AddCarViewController] deselect car: car not found.");
} else {
[self setCarArray:mutableFetchResults];
}
Doing this will hold onto all the objects in your managed object context, so when you want to modify a Car, you can find it there instead of fetching again. If you need to sort it, you can apply a sort descriptor initialized with one of your attributes as a key. For example:
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:#"selected" ascending:YES];
[carArray sortUsingDescriptors:[NSArray arrayWithObject:sorter]];
This will sort the Cars based on whether they are selected or not.
As for why you're getting an error, that could be one of many reasons. Typically the MOC will fail to save if one of your attributes is set to nil, so that might be the cause. You can get some detail from it if you set something like the following up
if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error %#", error);
}
This will return the actual error you ran into. It would also be a good idea to set up a log to make sure you have Car specified. Something like NSLog(#"selected car %#", carToSave); just to be safe

Core Data Relationships fetch

Ok, I thought I had this but I am not getting the results that I am expecting. Hopefully someone can help.
I have two entities Person and Timesheet with one attribute to-many relationship:
Person.timesheet<--->>Timesheet.user.
The code below works but when I try to add a second timesheet entry it seems to override the first?
I have looked at the Apple Docs and they are a little vague on this subject.
//Add
NSManagedObjectContext *context = self.managedObjectContext;
Person *personAdded = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
Timesheet *timesheet = [NSEntityDescription insertNewObjectForEntityForName:#"Timesheet" inManagedObjectContext:context];;
timesheet.time = #"10:00 Friday";
timesheet.timestamp = [NSDate date];
NSSet *timesheetSet = [NSSet setWithObject:timesheet];
personAdded.name = #"Darren";
personAdded.job = #"Job to be Done";
personAdded.timesheet = timesheetSet;
NSError *error = nil;
[context save:&error];
if (error) {
NSLog(#"[ERROR] COREDATA: Save raised an error - '%#'", [error description]);
}
NSLog(#"[SUCCESS] COREDATA: Inserted new User to database!");
// Load
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: personEntity];
error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (!results || error) {
NSLog(#"[ERROR] COREDATA: Fetch request raised an error - '%#'", [error description]);
[request release];
}
NSLog(#"Results: %#",results);
Person *firstUser = [results objectAtIndex:0];
NSLog(#"First User's name: %#",firstUser.name);
NSLog(#"First User's time %#",[[firstUser.timesheet anyObject] valueForKeyPath:#"timestamp"]);
I am wondering if it could be because I am actually setting the Person.timesheet key with the NSSet and not the actual Table? OR could it be that I am not calling the results correctly?
Thanks,
Darren
You should have a generated "CoreDataGeneratedAccessors" method in your Person.h file which gives you a method
[personAdded addTimesheet:timesheetSet];
which creates the links for you ?
Core Data normally generates an add and a remove method for any relationships you define.
Post your person.h file if this is not clear.

How to determine number of objects in one-to-many relationship in CoreData

So, I've got a one-to-many relationship of Companies to Employees in CoreData (using a SQLite backend on iOS, if that's relevant). I want to create a predicate that only returns Companies that have 0 Employees associated with them. I could do it by getting all the Companies and iterating over them, but that would be (I assume) much slower.
Any ideas?
Thanks,
-Aaron
After trying #falconcreek's answer and getting an error (described in my comment on his answer), I did some googling and determined that the answer was
NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees.#count == 0"];
Now everything works über efficiently. Thanks!
Assuming your Company -> Employee relationship is named "employees"
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// the following doesn't work
// NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees = nil OR employees[SIZE] = 0"];
// use #count instead
NSPredicate *noEmployeesPredicate = [NSPredicate predicateWithFormat:#"employees = nil OR employees.#count == 0"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (error)
{
// Deal with error...
}

Core Data: Returning properties depending on a value

can anyone guide me how to create a fetch request that will query an entity and returns any properties that qualify my criteria.
Here's what I have. I have an entity that has 35 properties, all are in types of float.
What I need was to see all properties of the entity which values was <= zero.
I know how to return the values of the properties but not how to return the name of the property.
Thanks,
CoreData doesn't return properties. It returns entities, which then have properties. In any case, you'd have to do something like this:
(The following is pseudo-code done from memory. Treat it accordingly.)
NSString *query = #"(property1 <= 0) && (property2 <= 0)";
NSPredicate *predicate = [NSPredicate predicateWithFormat:query];
NSEntityDescription *entity = [NSEntityDescription entityDescriptionForName:#"Entity" inManagedObjectContext:context];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
NSError *error = nil;
NSArray *entities = [context executeFetchRequest:fetch error:&error];
// Let's just assume we got one
NSManagedObject *obj = [entities objectAtIndex:0];
float value = [obj valueForKey:#"property1"];
Or something like that.