Core data and data integrity: read operations vs write operations. How to protect? - ios

I have what I think is a simple task.
I have a method called [self getPerson] that makes a simple GET request from a web service for a Person that returns some JSON and then transforms the JSON into an NSManagedObject. checks for an existing identical Person NSManagedObject, and if none is found, saves the Person into core data. No problem.
However, If I fire off this method twice in a row, I get two Person NSMangedObjects persisted into Core Data. For example:
[self getPerson];
[self getPerson]; ---> yields duplicate `Person` objects saved in core data, no good.
How can I ensure that only one Person object is saved in Core Data (no duplicates allowed)?
I know the issue, I just don't know how to fix it. The issue is that I need a transaction. When [self getPerson] fires the first time, the method checks for an already existing identical Person object, finds none, and saves a new Person into core data. This is correct. When I fire [self getPerson] the second time, the method checks for an already existing Person object, doesn't see one, and is then persisting another Person object. This is not correct. I would like to make it so that the second time, and third time, and fourth time, to the 1000th time, checking for an existing Person object will only occur after the managedObjectContext saveoperation is done. Right now the check for an existing object is happening so fast (before the save is done).
Do I need a serial queue? If so, should this be a dispatch_async or dispatch_sync? I've even toyed with the idea of trying to use a performSelectorWithDelay trick.

Once you create the object it will exist in the database regardless of you calling save. So you should not create a managed object if one exists already. It's not entirely clear what your code logic is but from your description you say you transform the JSON to a managed object and then you check for an identical existing one and if none is found you save. Well when you create the managed object you have created it, so it's too late to check if an identical one exists. Saving does not create the object it just saves it to the store if it hasn't already been saved.
So first check if an person object exists with the attributes in the JSON and if not then create a managed object.

Well, in this case a serial queue will ensure that operations are performed in the correct manner.
From you question, maybe I'm missing something, I cannot understand if the getPerson method is responsible to both get and save data. If not, you should do it.
Anyway, if you use JSON and the person you retrieve form the server has a unique identifier, you should use that to query against Core Data and verify if it exists or not. The correct manner to do it is to implement Implementing Find-or-Create Efficiently.
A simple question. Is the any reason for calling the getPerson twice? Could you not prevent it using a flag (or a transient property)? Just simple ideas.

Related

NSManagedObjectContext refreshObject causes duplicates in NSFetchedResultsController

I have an issue where if I make changes to a core data object, save then refreshing the object, causes my NSFetchedResultsController to show a duplicate object. I think I understand what's going on, but I'm looking for someone to confirm, and also to hopefully give some more detail as to why.
To explain in more dtail I have two entities, Fixture and Position. A Fixture has many Positions, and a Position belongs to only one Fixture. To reproduce the issue I do the following:
Fetch all Positions.
Modify some value (any one) on that objects Fixture. I.E foo.fixture.name = "foobar"
Save the context
Refresh objects by calling context.refreshAllObjects, or context.refreshObject(foo, mergeChanges: false/true).
I have a tableview using a fetched results controller which displays Fixures. After doing the above the tableview will display duplicates for each item (it doesn't matter if I use the delegate methods of the FRC to do the update or I just reload the tableview).
It appears what's happening is that the refresh invalidates the objects that the FRC knows about, while at the same time gets knowledge of another set of objects. If, as step #5, I call frc.performFetch() then the problem goes away.
Other things to note:
No matter how many times I run the code I only get two of each object (I'm using a random button to trigger it for testing).
init(entityName, context) is called on my Fixture subclass as soon as I access the Fixture property of my object during the next code run (i.e after refresh was called).
In my sample everything is taking place on the same context (though it happens with child contexts as well)
To give some more context as to how I got myself in this situation in the first place users can click on a fixture in the list and then ultimately narrow down on a single position a few screens later where they can perform actions that modify the fixture. There are other active areas of the application at this point that are listening to the NSManagedObjectContextDidSaveNotification and I want them to update their objects so they can display the correct data, which is why I was calling refresh.
I've dug around in the docs and I can't see anything that specifically explains my theory that updating the context causes NSFetchedResultsController to have invalid objects. Can anyone shed some light on this behavior?
First, you really should not be overriding -init... on a NSManagedObject. That is one of the methods that you are strongly discouraged from overriding and can very easily be the source of your issues.
Second, your entire UI should be using a single instance of the NSManagedObjectContext that is associated to the main queue and therefore there should only be ONE instance of any particular entity in your UI. If you have multiple contexts you are just making things more complicated for yourself. If you are using a single context keep in mind that no matter how many times you fetch an object against that context you will get the exact same pointer back.
The NSFetchedResultsController will never create objects, it only fetches and provides them for display. Therefore the NSFetchedResultsController is only reporting the fact that you have created this duplication somewhere else in your code.
Now some questions, do these duplicates get pushed down to disk?
Can you see them in the store file on disk?
When you re-launch your application are the duplicates still there?
When you put a print in your custom -init methods on the NSManagedObject; do they fire more than once?

Core Data--pass name of entity or reference to entire entity?

I'm making a simple bank account tracker, for self-instructional purposes. I'm using Core Data to store three entities, related as in the screenshot:
WMMGTransaction objects are simply stored as they are recorded, and extracted as needed to feed tableviews and detail views. This will be done via NSFetchedResultsController and a predicate. I'm using MagicalRecord to access Core Data, if that matters.
My question is this:
When I pass WMMGAccount data from one VC to another, such as when creating a new account, or when selecting one from a list (via delegation as a rule), does it matter if I pass a reference to the entire entity, or can I just use an NSString bearing the .name of the account and identify the account when required with a predicate and an NSFetchedResultsController? I guess this is a strategy question, and may generate discussion, rather than having a cut and dried answer, but I'm wrestling with it, so I thought I'd ask.
It sounds like you're asking if you should pass an object to the code that needs it, or if you should pass information that could be used to look up the same object again.
Unless you need to use the managed object on a different thread or queue, you should always pass the actual object. No sense re-fetching an object you already have. It's extra work and code complexity that (unless there are some unusual extenuating details you didn't mention) won't help in any way.
If you are needing to use the object on a different queue or thread, passing information that can be used to look it up is the correct approach. But in that case-- don't pass the value of one of the properties. Use the managed object ID.
Core Data won't force name values to be unique, while the object's managedObjectID is unique. It's also faster when retrieving the object, because you can use objectForID: or existingObjectForID: instead of performing a fetch.

Cocoa Core Data - best practice to check if object with the same property already exists

I would like to check with you the best practice to solve the following task in Core Data framework. In my model one of the property for one object type must be unique. Let's say I have object Account - the property name must be unique - it is not allowed to have 2 accounts with same name.
There are 2 possibilities:
either I execute validation before I call insert into context -> at this point my new object is still not inserted into context, so I can call fetch from context and check if there is already account with particular name
or I overwrite built in validation methods and put my validation there as mentioned here:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdValidation.html#//apple_ref/doc/uid/TP40004807-SW1 - all these validation methods are called only after context is saved
I personally like second case, because my validation would be in model. But the problem is that at this point the object is already inserted into context and therefore if I call fetch, the validation always fails, because also the name of new object counts (even if is still not saved permanently). There is a solution for this however. I can check solely the permanent saved objects by creating new temporary managed object context and call fetch
Is this the best practice to execute such a validation. Or am I missing something or there is better way to do it ?
BR
Lubos
I would go about it in the following way. In general, it is advisable to avoid the complexities of multiple contexts, although that too is a pattern demoed by Apple.
Create the new managed object, insert it into the context. Check against existing names. If the name is not valid, prompt for a different name. Repeat until the name is valid. If the user breaks off the process or times out, delete the object.
If you can do that before saving, discarding the object could be as simple as calling [context rollback];.
If you do this in a separate controller, you could do it in a child context. If the user terminates the process, you just throw away the context altogether without saving.
If you find a name exists but suspect that it is the name being created you can easily check it it is the same object (you already have a reference to it). You could also do a fetch (or, more efficiently, countForFetchRequest) with a predicate that excludes this particular object.
NSPredicate(format:"name = %# && (not self = %#)", account.name, account)

How to write changes from one ManagedObject to similar ManagedObjects

I am new with Core Data and have a problem which sounds trivial to solve (at least thinking in SQL) but I can't get my head around that with Core Data.
What I'm trying to do is the following: I have a fetched ManagedObject, do some changes and save it again. This ManagedObject has an attribute id. I want to write the changes I made to this ManagedObject to all the ManagedObjects with the same id.
I was thinking to overwrite willSave: and fetching the other ManagedObjects with the same id there but this won't work because I would encounter an infinite loop there.
Can somebody give me a hint on how to progress from here? Thanks in advance
You could make willSave work, but it isn't going to be a nice bit of code to ignore all of the invalid triggers.
It's better to have a class which manages this functionality, pass in the new data value and the attribute id and allow it to do the fetch and update all of the fetched objects (and trigger the save).
I would, indeed, try to find some better way to deal with it, because actually you should't think of Core Data as of SQL with its triggers.
But actually you can indeed use willSave method and avoid infinite loop. See
NSManagedObject Class Reference willSave method
If you change property values using primitive accessors, you avoid the possibility of infinite recursion, but Core Data will not notice the change you make.
So basically in your willSave method you'll need to call some fetchRequest to get all instances of the same Entity, then loop through them and update using primitive accessor: setPrimitiveValue:forKey:
Also I would advice to verify objects in loop whether they are removed (-isDeleted) and, probably, whether that object is not your current one (by comparing managedObjectIDs)

Attaching object to EF 4 context from MVC view?

I am not sure if that makes any sense, but here is an example.
I have a Category object, that my Service hands to the Controller, which uses AutoMapper to create a CategoryViewModel. Hand that off to the view, serve it to the client.
Now when that gets posted back, AutoMapper creates a Category from the Model sent back, and I hand it to the Service that gives it to the Repository to persist to the database.
My question is, what is the correct way of doing this? I assume the object is a detached object when posted back and I need to attach it to the context, mark it dirty and save changes?
Basically two ways of doing the update of the entity:
Attach the entity to the context, mark it as modified using ObjectStateManager.ChangeObjectState Method, call ObjectContext.SaveChanges Method
Load the original entity from DB, apply changes to the original using ObjectContext.ApplyCurrentValues<TEntity> Method, call ObjectContext.SaveChanges Method
Each of those have their own pros and cons. For example the 1st one does not make round trip to get the original entity but fails to address concurrency as well as tries to update every property of the entity, while the 2nd one works best when employing optimistic concurrency, updates only changed properties, but it does make extra trip to Db to get the original entity.
"I assume the object is a detached object when posted back and I need to attach it to the context, mark it dirty and save changes?"
Yes.
Any one of the links on this page should help:
http://www.google.com/search?rlz=1C1CHFX_enUS410US410&sourceid=chrome&ie=UTF-8&q=working+with+dicsonnected+entities+entity+framework

Resources