iOS CoreData commit sub transaction but cancel main transaction - ios

I have the following core data model:
A -> B (1 to 1)
A -> C (1 to n)
I created a view controller VC to edit any properties of an instance of A. In that VC I would like to add the possibility to create new instances of C so the user can add new Cs on the fly if necessary. Right now I am doing this by just adding these to the viewContext and saving it. That works but has a little hick up. Once I saved the new C to the context I cannot rollback any changes that have been made to A previously.
So I studied this tutorial and found that any changes that I consider separate should be applied in its own child context. I understand that but I still have one question: The tutorial says that any changes made to a childContext are pushed to its parent on save, but it will never be written to disk unless I save the parentContext. Now if I do not want to save the parent context because the user hit the cancel button to rollback other changes, how can I still keep the new Cs?

Don't save data directly to the ManagedObject (CoreData object) for either of the entities A, B or C, rather you should create a model which you should update according to the user input or any data source. You need to create managed objects for A, B or C and set value for respective attributes whenever user confirms to save data. That means if user hits cancel, data won't be saved to CoreData but you will be able to find the previous data in the model throught out the model object lifetime.

Related

Deleting NSManagedObjectContext

I am trying to make an application where a user can edit attributes of a managedObject in a view, and select either Done to persist the changes, or Cancel to undo the changes.
To implement this behavior, I plan to do the following-
When the view controller is loaded to edit the managedObject, create a backupManagedObject which is a clone of the original managedObject.
This backupManagedObject is created in a separate child ManagedObjectContext.
Let the user edit the original managedObject.
If:
Done is pressed, the original managedObject is persisted, and backup is deleted
Cancel is pressed, the backupManagedObject is cloned into original managedObject and the backup is deleted
My question is, once I am done with the backupManagedObject, how can I delete the childManagedObjectContext which will have no more managed objects and I don't plan to use them anymore (for every new view controller, I plan to just create a new child managed object context and destroy it once view controller is done).
You should do this the other way around:
When you load your editing View Controller, create new Managed Object Context that is the child of your main one, let's call it the editingMOC.
Do the edits to the editingMOC, and if you want to persist them, save the editingMOC (this will propagate changes to the mainMOC), and then save the mainMOC to save the data to the persistent store.
If you wish to discard the changes done to the editingMOC, simply do not save them and let the context get dealloc'd.
In general, a managed object context is release the same way any other object in Objective-C is released and deallocated.
If you are using ARC, simply set the property to nil when you no longer need it and it will be destroyed along with any unsaved changes.
However, your approach for this problem is a bit complicated.
You could simply create a new "editing" child context, fetch the objects you like to edit in that context and make changes to the objects.
If the user decide to commit the changes, save the context (up to the store), and if not, simply destroy the context.
An easier way is to simply create the view and populate the UI controls (text fields, etc.) using data from the NSManagedObject attributes. If the user makes any edits then set a flag so you know if changes are made and then when they select Done update the NSManagedObject attributes using values from the UI controls and save the MOC. If they select Cancel then don't do anything.
See this link for a video showing an app using a similar approach for editing Core Data records on iOS. The OSX app uses standard NSPersistentDocument undo manager.
http://ossh.com.au/design-and-technology/software-development/uimanageddocument-icloud-integration/os-x-and-ios-app-integration-with-core-data-and-icloud/

What's the correct way to handle new breeze entities that might be thrown away?

I'm sure I've created my own pain here, but I'm struggling to understand the correct sequence of events to manage creating new entities in my scenario.
In my model I have two objects, ObjectA and ObjectB that both inherit from BaseObject, obviously each with their own additional properties.
In my view, as most of the information is the same, I want the user to be able to just select an option as to which one to create. So they fill out SharedProperty1 and SharedProperty2 (which is a collection navigation property), select an option as to if they want an A or B object, and then fill in a final page which has the last object specific properties.
As I didn't know which entity to create until the user had selected this option, I built a an object in the viewmodel to handle this temporary data. As part of that while they are filling out SharedProperty2 (the collection), as they add new ChildObjects, I create them with entityManager.createEntity('ChildObject'). Then when they reach the end, I create either ObjectA or ObjectB entity and add the child entitites (and other properties) and then try and save.
The problem is it never saves correctly, but I get different results depending on which approach I take. So because the user could just abort the new object process, I was creating the ChildObjects with EntityState.Detached because I thought that would be easier if they got thrown away. I realised though that all the entities created in this way get the id key 0. So then I fixed the keys while I was adding the ChildEntites to the parent (either ObjectA or ObjectB), by assigning them decreasing negative numbers (ie: -1, -2, etc). This resulted in some crazy server-side behaviour with only some entities being saved to the db and complaints of conflicting foreign keys.
This also had a bad smell that I hadn't understood this correctly, and I'd made a mess of it. So now I tried just creating the entities normally (ie: without the Detached flag), and they all get their own unique keys (again breeze appears to follow -1, -2, etc), but now when I try to copy them from my temporary viewmodel collection to the parent object collection, I get the error that an entity with this key is already attached. So now I can't even build up the correct model to save.
I still think I've not understood quite correctly how to handle this, so some pointers would be deeply appreciated.
To head off what I suspect will be a question, why I didn't use RejectChanges to handle the entities being thrown away. Basically a user can add a ChildObject (object gets created by breeze entityManager, added to viewmodel collection, bound to UI), and then decide to just remove it again (currently just gets removed from viewmodel collection) before they save their data. If I used reject changes I would throw away other important entites. I think I'm now going to be a good boy and use the proper detach method if someone removes the ChildObject in the view.
If I understand your question correctly, you are trying to create some properties and then add them to a parent objects collection when saving. Correct me if I am wrong, but Breeze not only supports this, but does so very efficiently. Having come from .NET and C# it was very difficult for me to grasp how easy this can be, but this is what I would do if I were you -
var childA = ko.observable();
var childB = ko.observable();
childA(entityManager.createEntity('ChildObject')); // populate your children
childB(entityManager.createEntity('ChildObject')); // populate your children
Then you can edit them in your view, and when you are ready to save simply add them to the collection.
var save = function() {
isSaving(true);
var parent = ko.observable();
return entityManager.getParent(parent, parentId)
.then(setParents)
.fail(catchError);
function setParents() {
childA().parent(parent());
childB().parent(parent());
entityManager.saveChanges()
.then(complete)
.fail(catchError);
function complete() {
isSaving(false);
return Q.resolve(); // Don't know if you have any unresolved conflicts
}
}
};
Basically in this manner we are -
A : Creating the entities
B : Editing them without performing any changes
C : When we call save we are setting their parent navigation property. In my prior ways (be it right or wrong) I would have simply set ParentId(parentId) and let EF figure out how to navigate but (pardon the pun) this is a breeze with Breeze. We could also just as easily pass in a parent observable and not have to go get it from the manager, it just depends on whether we have it already or not.
Another way you could do this if you want to manage the entities separately is to save a single entity at a time with entityManager.saveChanges([childA]) as they are ready. Just pass in an array with a single entity that you want to save. This may be useful if you are working on multiple entities but they aren't all ready for saving and you need to navigate around your app. Unless you call cancelChanges() Breeze will just keep the entity in cache until you are ready to use it again. In this manner, just make a call for entities in the isAdded() state and you can pull 'em back in and edit again.

Why do I need to remove relationships before deleting an object in core data?

I have a program in which I'm using CoreData to manage both permanent and temporary data.
In one instance, I abort the addition of some data by deleting the object when the user presses cancel. But when I hit the next view, the data is still there, attached to it's parent core data object.
[self.bar removeFoosObject:self.foo];//Why do I need this line?
[self.foo.managedObjectContext deleteObject:self.foo];
I eventually solved this by manually removing the child object from it's parent -- but isn't that something core data handles automatically? Why should I need the first line?
I ran some test code, and Foo is definitely deleted -- the code that it was mucking up let me check, and it's MOC has been set to nil. The memory exists, but it should be very, very dead...
You have to manually do this because you have your deletion rules set wrong. Check the Relationship Delete Rules in the following apple documentation.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html
Or it could also be that the relationship is NOT set as optional between the parent and child object.
And also after you delete the object you should save the database to synchronize it.

Where in code is the best place to do a core data save?

I have an application that is mainly a heirachy. Object A has many object B's and Object B has many Object C's.
I display them in my iOS application via a navigation type application, and A is on the front page, you push to the B's and push to the C's.
I want to be able to save each instance of my A, B, C objects (which are custom classes) but I'm not too sure when to save and read them as the table view displaying data about A, B and C need to be populated when/before that view is pushed.
Should I be reading it out in didSelectRowAtIndexPath? or something else? and what about saving? should that take place during tapping back or some other place? in the class itself perhaps?
Sam
You should probably save whenever the user finishes an edit.
You should get your B's once you need them, etc. When the user taps on a row in the table view that displays all the A's, then get the B's that relate to that particular A object. You have two options here: (1) Fetch them with an NSFetchRequest / NSFetchedResultsController or (2) traverse the A object's to-many relationship that points to the B objects for that A objects.
As for saving: Note that saves might take a while if you've edited a lot. There are some advanced techniques you can use with iOS 5 with nested contests (-parentContext) which allow you to save on a different (non-main) thread. You have to make sure, though, that your safe completes before you're suspended, because the system is allowed to kill your process while you're in the background.

Wrong object added to ObjectContext (_addedEntityStore)

I have a wizard in my asp.net MVC application which is built upon this SoG - RESTFUL wizard guide.
In order to understand the problem I will first explain the situation below. For this I will use a dummy situation, we try to create a person with an address. Again, this is just a dummy situation!
The situation
At start, the object (person) to be created and saved using the wizard is created as a blank object (person) and will be filled during the wizard 'stage'. In between the various wizard steps the person object is stored in the SESSION and retrieved with a key. This object has a relation with a sub-object. For example, an address. This address can either be retrieved from a DB using a dropdown-menu in the wizard or can be created in the wizard itself. This means that at creation I will create an empty address-object so we have the following initialization:
Persons p = new Persons();
p.Addressess = new Addressess()
This object is passed to the FormViewModel and used in the Wizard Form. Here I have a drop-down enabling to choose: (1) *create new address for person", which fills the passed empty-address object with data using the usual ways (TryUpdateModel()) and (2) "address x", addresses to select from your DB.
Selecting one of the addresses in the dropdown will, in the controller POST method, retrieve this object from DB and couple it to the person.Addresses. In code:
p.Addresses = repository.GetContactByID(id);
The problem
Everything works fine while running through the wizard pages. The problem occurs when I call the savechanges(). While in a final overview the complete object to be added is shown correctly (person info + address info as selected / passed), also an empty address is saved to the database.
repository.SaveChanges();
This will try to add an empty Addresses to the DB which will introduce a ModelState error since Addresses have some non-nullable's which are not set for the empty object.
** My current idea **
My current thinking is that the empty object created at start for the blank object is somehow placed in added state (objectcontext) when I first couple it to person.Addresses. Can this be the case? What would be the correct way to do what I want? Can I manually delete things from the object context _addedEntityStore ?
Additional Info
Selection of an address in the dropdown will force a form.submit to the POST controller method and consecutively it will reload the form with updated selection info and the input fields for the new address (in case a new one is wanted) set to "disabled" so you only see the info but cannot edit an existing address.
Also, only one objectContext is used which is saved in the SESSION.
Any reason you can't just leave p.Addressess as null when you first create a Persons object and only add a new Address if that's what they select in the wizard?
(And BTW why the odd pluralization? Do they have one address or several?)
The other issue you will encounter is that you will be loading existing addresses in one context and saving them in another (assuming you are using a context per request approach) - you'll need to detach the existing addresses from one context they were loaded in and attach them to the context used to save Person at the end of the wizard.
It might be easier to include a separate AddressNew and an AddressExisting object in the Session state (one or the other is null). At the end, if AddressNew is present, add it to the Person and save changes. If AddressExisting is present, attach it to the context, add it to the Person and save changes.
Although Hightechrider is right about using an object context for every unit of work I post the simple solution I used for now. When you somehow (forced, chosen, whatever) have your object context stored in SESSION you can solve the above problem simply by calling:
repository.Detach(person.Addresses)
Which will detach the old object from the context (not saved yet to DB). And after detaching, attach the new object you want to have it linked to.
person.Addresses = repository.GetAddressByID(id);
// or
person.Addresses = new Addresses();
Though I do recommend rewriting your wizard if you need to do this and have the time and power :).
edit
note that Detach only detaches the object supplied, not its related objects (!).

Resources