Search code examples
xcodecore-dataentity-relationshipcascadensfetchrequest

Struggling with CoreData relationships and deleting objects


I'm relatively new to Objective-C and trying to work my way through learning CoreData, albeit with tons of Google searches. I have a good grasp of mySQL and relational tables, but I can't seem to wrap my mind around how to get entities to relate to each other in CoreData.

I like to create little projects for myself when learning something like CoreData, so what I put together was the simple concept of an automotive service tracker (like oil changes, brake work, etc).

I've got 2 entities representing Vehicles and Oil Changes like so ...

enter image description here

and

enter image description here

... as well as their respective relationships. I'm not certain if this would require a one-to-one or one-to-many relationship as a vehicle would have many oil changes, but there would also be many vehicles ... so I'm guessing that's the first thing I need some help understanding.

I'm using SQLLite Manager to watch what's going on with the data model. When I add Vehicles to my Vehicle entity (basically saving UITextFields) I can see that the objects are in the correct entity ...

enter image description here

... and when I add Oil Changes to a particular Vehicle, I can see that those objects are being put in their proper entity as well ...

enter image description here

.. but as you can see from the following, there seems to be a disconnect when trying to associate a particular oil change with a particular vehicle ...

enter image description here

Now, for the important questions ...

1). How do I get individual oil changes to relate only to their respective vehicles? Right now all oil changes are displayed regardless of which vehicle is selected. I'm sure that this would be related to updating my fetched results query, however as you can see from the displayed tables, I'm not getting the respective oil change to the respective vehicle.

2). And this might be solved by understanding question 1 ... but if I delete a particular vehicle, then I obviously need to also delete any oil changes that were related to that vehicle.

My save method is below.

- (IBAction)save:(id)sender {
Oil *oil = [NSEntityDescription insertNewObjectForEntityForName:@"Oil" inManagedObjectContext:self.managedObjectContext];
Vehicle *vehicle = [NSEntityDescription insertNewObjectForEntityForName:@"Vehicle" inManagedObjectContext:self.managedObjectContext];

// Which Vehicle
// newOil.vehicle = whichVehicle.text;
oil.vehicle = vehicle; // not working

// Format: Mileage
NSNumberFormatter *numberFormat = [[NSNumberFormatter alloc] init];
[numberFormat setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *myNumber = [numberFormat numberFromString:oilMileage.text];
oil.oilMileage = myNumber;

// Format: Date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
[dateFormat setDateFormat:@"MM-dd-yyyy"];
oil.oilDate = [dateFormat dateFromString:oilDate.text];

// Format: Notes
oil.oilNotes = oilNotes.text;

// save context
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate saveContext]; }

->

->

->

->

OK, I'm attempting to integrate what Dan Shelly has suggested (which makes total sense) however I've run into a snag that I need a little more guidance on.

My mainVC isn't using a tableView to display my Vehicles (I've limited the total vehicles to 4 and I'm representing them with icons and tying them back to vehicleIDs in CoreData) ... anyway, since it's not a tableView I can't use the following:

Vehicle *existingVehicle = (Vehicle *)[self.fetchedResultsController objectAtIndexPath:indexPath];

... therefore I'm having trouble creating an instance to the fetched Vehicle object that I can then pass to the OilViewController. I've got a fetchRequest that's returning the correct vehicleID out of CoreData, but I need to know how to actually create the object so that I can pass that over to the OilViewController. My fetchRequest looks like this ...

- (void)fetchObjects {
NSLog(@"DVC-fetching Objects\n\n");

// Create Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Vehicle" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];

// Set Search Criteria
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"vehicleID == %@", vehicleID];
[fetchRequest setPredicate:predicate];

NSError *error;
NSArray *array = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

NSLog(@"Object ID: %@", [array valueForKey:@"objectID"]);
NSManagedObjectID *moID = [array valueForKey:@"objectID"];

if (array != nil) {
    NSUInteger count = [array count]; // May be 0 if the object has been deleted.
    Vehicle *existingVehicle = (Vehicle *)[self.managedObjectContext existingObjectWithID:moID error:&error];
    NSLog(@"Count: %lu", (unsigned long)count);
    NSLog(@"Fetched Vehicle ID: %@",predicate);
    NSLog(@"existingVehicle: %@", existingVehicle);

} else {
    // Deal with error.
}

}

but I'm getting the following error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI isTemporaryID]: unrecognized selector sent to instance 0x11d5b660'

  • The moID is returning correctly as "0x8bb1980 x-coredata://6681B331-29F0-448B-82F1-4660E033A460/Vehicle/p1"
  • The array count is returning "1" [correct]
  • And the FetchVehicle ID is returning "vehicleID == 1" [also correct]

How do I create an instance of Vehicle to this returned object so that I can pass it over??


Solution

  • 1) How do I get individual oil changes to relate only to their respective vehicles?

    You already do ... only you are currently relating the oil changes to an empty Vehicle object. this is also visible in your vehicle table (Z_PK = [3,4,5]).

    When you need to add a new oil change, you probably already have access to the relative Vehicle object this oil change is adde for.
    This Vehicle object is probably the one you select in you vehicles view controller.

    so instead of inserting a Vehicle object each time you create an oil change, simply set the relationship to an existing vehicle (you are building an object graph, think in terms of existing object and attaching them to each other).

    2) Deletion:

    from the pictures it seems that you set your deletion rules as they should be in order to achieve your goal. when you delete a Vehicle object the cascade rule will delete the related oil change object along with it. when you delete an oil change object the related Vehicle oil relationship will be nullified.

    So to summarise:

    Get an existing Vehicle object:

    //in your vehicle view controller :
    Vehicle* existingVehicle = (Vehicle*)[self.fetchedResultsController objectAtIndexPath:indexPath];
    

    Pass this object along to your "oil change view controller". When you add a new oil change, set the vehicle to the existing object:

    Oil *oil = [NSEntityDescription insertNewObjectForEntityForName:@"Oil" inManagedObjectContext:self.managedObjectContext];
    oil.vehicle = existingVehicle; // the one you passed along to this scope
    //The rest of the oil content setting
    //DON'T FORGET TO SAVE!
    

    You will probably want to change you relationship type to a to-many type of relation ship (a Vehicle may have many oil changes).
    This will not change your oil change creation code.

    To view oil changes for a specific Vehicle object, you will have to use the existing vehicle object you passed to the oil change view controller and use it in the predicate of the FRC fetch request:

    NSPredicate* p = [NSPredicate predicateWithFormat:@"vehicle = %@",existingVehicle];
    fetchRequest.predicate = p;