What does NSManagedObjectContext save do in terms of SQLite? - ios

I'm porting some iOS persistence functionality to Android and trying to understand save(), in order to replicate the functionality in Android (pure SQLite).
Documentation says:
save:
Attempts to commit unsaved changes to registered objects to the receiver’s parent store.
Doesn't help a lot.
I know that iOS uses SQLite so this has to translate to SQLite somehow.
Looks like save is an upsert - will insert the data if not there yet, and otherwise update.
If this is true (also if not, if the question is still valid) - how is determined which row to update? I don't see how to add unique in xcode, so if I have e.g:
id | name | price
1 | apple | 2.0
2 | lemon | 1.0
with "id" being the internal row id,
and I get new model data "lemon" -> 3.0, when I update the moc, how does the database know that it has to update this row?:
2 | lemon | 1.0
In SQlite I would add a unique on the name, but I don't know how it's implemented in iOS.
I'm not an iOS dev, sorry for possibly super -ignorant or -strange question.
Thanks.

It is really difficult to discuss Core Data in terms of databases because it is not a database. It uses one to persist data but that is just about it.
Looks like save is an upsert - will insert the data if not there yet, and otherwise update.
An NSManagedObjectContext is the current state of not just one object (or row in database terms) but multiple. So when you ask the NSManagedObjectContext to 'save' it is saving the state of all the objects in the context. If an object is new, it will be the equivalent of an insert. If the object already exists, it will be the equivalent of an update. However, if at some point an object is deleted, the 'save' method will also remove the object from the SQLite database. The 'save' method specifically saves the state of the NSManagedObjectContext.
If this is true (also if not, if the question is still valid) - how is
determined which row to update? I don't see how to add unique in xcode
That is because Core Data handles the unique identity of objects. There is no default 'id' column to place a unique identifier. However, you can create an attribute (i.e. column/field) to hold a unique identifier if the database will be persisted across many devices, which I personally had to do at one time since the 'objectID' is not practical to use. In Android, you will have to maintain the unique identity of each row yourself unless you opt to use auto incrementation.
when I update the moc, how does the database know that it has to
update this row?
At one point or another, you ask the NSManagedObjectContext to insert a new "Entity" (i.e. table):
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:#"EntityName" inManagedObjectContext:managedObjectContext];
To update an entity, you could retrieve it by using:
NSManagedObject *managedObject = [managedObjectContext objectWithID:managedObject.objectID];
Make any adjustments and then 'save' the NSManagedObjectContext. The objectID is its unique identifier that was automatically assigned when inserted. Core Data handles the boiler plate code of inserting and updating rows so you end up with an abstract version as seen in the examples. If you save a few NSManagedObjects and open the SQLite file, you will find that it is very similar to any other database, other than a few Core Data specific fields that is uses for management.
I would suggest creating a new Master Detail Application project, run it in the simulator, save a couple entries, and open the SQLite file. You can find it in
/Users/<username>/Library/Application Support/iPhone Simulator/<iOS Version>/Applications/<Application UDID>/Documents/
Opening the SQLite file will show you that the database Core Data maintains is very similar to any other SQLite database and may help out with understanding the processes.

I don't know the following to be true, but I think I'm not far off.
An NSManagedObjectContext has a reference to objects (NSManagedObject) that are composed using the data from the SQLite database. These objects all have the objectID property, which is a unique identifier to the row in the SQLite database allowing you to uniquely, even between contexts, identify an object/row. When you change an object's property, this doesn't actually change anything in the database. The context knows about the changes, and when you call save:, it will go to the database and update all the records.
This is always an UPDATE, as you have to call -[NSEntityDescription insertNewObjectForEntityForName:InManagedObjectContext] to get a reference to an object. At that point, a record is already inserted and it is given an objectID.

NSManagedObjectContext is kind of a representation of the data model. It is from the framework called CoreData. By using CoreData, we do not manipulate the SQLite database directly. Which means we do not write any SQL queries, we just do all the update, insert or delete on NSManagedObjectContext. And when we call save(), NSManagedObjectContext will tell the database which row was updated, which row was deleted or which row was inserted. And here is another question which might help you to understand more about NSManagedObjectContext.

Related

Change relationship of NSManagedObject to different context

This is a follow up to an earlier question: Core Data: change delete rule programmatically.
I'd like to rephrase my question, and will do that here.
Briefly, my app allows updating entries from a 3rd party database, but I'd like to keep user annotations. So my workflow is:
iterate over all entities
download external xml and parse it into a new entity
if user annotations, change their relationship from old entity to new entity
delete old entity
During the import, the old entity is in the main context, the new entity is in a temporary import context.
Number 3 gives me problems, if I just change the relationship, then they don't show if I update my UI. If I use the objectID to get the annotation and then change the relationship as follows:
NSManagedObjectID *objectId = oldAnnotation.objectID;
Annotation *newAnnotation = [importContext objectWithID: objectId];
[newEntry addAnnotationObject: newAnnotation];
It's still not working - it's not showing up.
EDIT: if I change the context in the second line to newEntry.managedObjectContext, I get an Illegal attempt to establish a relationship 'foo' between objects in different contexts error.
What am I missing?
UPDATE: After some late-night hair-pulling debugging, I found that I when I was fetching the newEntry, I was actually fetching the oldEntry, therefore none of the changes would show up. The answer below by #Mundi pointed me in the right direction.
Copying the old annotations worked using my code above, followed by copying the attributes. For some user input with relationships in itself, I had to do a "Deep Copy", which I found here: How can I duplicate, or copy a Core Data Managed Object?.
I think creating a new entity and deleting the old one is a problematic strategy. You should try to properly update the existing entities and only create new ones if they do not yet exist.
Whenever I need an object from a different context, I fetch it. That being said, your object id code should work. However, there could be all sorts of other glitches, that you should check:
Did you save the importContext?
Did you save its parent context, presumably the main context?
Was the modified object graph saved to the persistent store?
Are you checking the results after you have saved?

Fix uneccessary copy of NSManagedObject

I'm sorry the title may mislead you, since I'm not so good at English. Let me describe my problem as below (You may skip to the TL;DR version at the bottom of this question).
In Coredata, I design a Product entity. In app, I download products from a server. It return JSON string, I defragment it then save to CoreData.
After sometimes has passed, I search a product from that server again, having some interaction with server. Now, I call the online product XProduct. This product may not exist in CoreData, and I also don't want to save it to CoreData since it may not belong to this system (it come from other warehouse, not my current warehouse).
Assume this XProduct has the same properties as Product, but not belong to CoreData, the developer from before has designed another Object, the XProduct, and copy everything (the code) from Product. Wow. The another difference between these two is, XProduct has some method to interact with server, like: - (void)updateStock:(NSInteger)qty;
Now, I want to upgrade the Product properties, I'll have to update the XProduct also. And I have to use these two separately, like:
id product = anArrayContainsProducts[indexPath.row];
if ([product isKindOfClass:[XProduct class]] {
// Some stuff with the xproduct
}
else {
// Probably the same display to the cell.
}
TL;DR
Basically, I want to create a scenario like this:
Get data from server.
Check existed in CoreData.
2 == true => add to array (also may update some data from server).
2 == false => create object (contains same structure as NSManagedObject from JSON dictionary => add to array.
The object created in step 4 will never exist in CoreData.
Questions
How can I create an NSManagedObject without having it add to NSMangedObjectContext and make sure the app would run fine?
If 1 is not encouragement, please suggest me a better approach to this. I really don't like to duplicate so many codes like that.
Update
I was thinking about inheritance (XProduct : Product) but it still make XProduct the subclass of NSManagedObject, so I don't think that is a good approach.
There are a couple of possibilities that might work.
One is just to create the managed objects but not insert them into a context. When you create a managed object, the context argument is allowed to be nil. For example, calling insertNewObjectForEntityForName(_:inManagedObjectContext:) with no context. That gives you an instance of the managed object that's not going to be saved. They have the same lifetime as any other object.
Another is to use a second Core Data stack for these objects, with an in-memory persistent store. If you use NSInMemoryStoreType when adding the persistent store (instead of NSSQLiteStoreType), you get a complete, working Core Data stack. Except that when you save changes, they only get saved in memory. It's not really persistent, since it disappears when the app exits, but aside from that it's exactly the same as any other Core Data stack.
I'd probably use the second approach, especially if these objects have any relationships, but either should work.

link two objects in CoreData

i am new in core data and i created 2 tables,Night and Session. i manage to create new object of Night and new object for Session. when i try this code:
Session * session = [NSEntityDescription insertNewObjectForEntityForName:#"Session" inManagedObjectContext:[[DataManager sharedManager] managedObjectContext]];
Night * night = [NSEntityDescription insertNewObjectForEntityForName:#"Night" inManagedObjectContext:[[DataManager sharedManager] managedObjectContext]];
night.sessions = [NSSet setWithObject:session];
the session is getting into the night and the cool thing is, when i Fetch this night and can get the session for the night using:
currentNight.Seesion
But i can't see this link in the DB tables :(
UPDATE:
I mean when i write night.sessions = [NSSet setWithObject:session]; i need to see in the table DB (yes in the DB.sqlite file).
i thought that i should see some thing there ...
Core Data is not a relational Database.It makes structure of their own.It defines the Database tables structure according to your Managed Objects.For debugging you can see what queries core data is firing on sqlite.This will show you how core data is getting data from these two tables.
You have to go Product -> Edit Scheme -> Then from the left panel select Run yourApp.app and go to the main panel's Arguments Tab.
There you can add an Argument Passed On Launch.
You should add -com.apple.CoreData.SQLDebug 1
Press OK and your are all set.
Than next time it will show all the queries it running to fetch data from your tables.
It's not clear to me what your question is. But:
A context is a scratchpad. Its contents will not be moved to the persistent store until you -save:. If you drop into the filing system and inspect your persistent store outside of your app without having saved, your changes will not be recorded there.
For all of the stores the on-disk format is undefined and implementation dependent. So inspecting them outside of Core Data is not intended to show any specific result.
Anecdotally, if you're using a SQLite store then you should look for a column called Z_SESSIONS or something similar. It'll be a multivalued column. Within it will be the row IDs of all linked sessions. Core Data stores relationships with appropriately named columns and direct row IDs, which are something SQLite supplies implicitly. It does not use an explicit foreign/primary key relationship.
To emphasise the point: that's an implementation-specific of Core Data. It's not defined to be any more reliable than exactly what ARM assembly LLVM will spit out for a particular code structure. It's as helpful to have a sense of it as to know about how the CPU tends to cache, to branch predict, etc, but you shouldn't expect to be able to take the SQLite file and use it elsewhere, or in any way interact with it other than via Core Data.

Optimistic locking support in NSIncrementalStore subclass

I am implementing a custom NSIncrementalStore subclass which uses a relational database for persistent storage. One of the things that I still struggle with is the support for optimistic locking.
(feel free to skip this lengthy description right to my question below)
I analyzed how Core Data's SQLite incremental store approaches this problem by examining SQL logs produced by it and came up with following conclusions:
Each entity table in the database has a Z_OPT column which indicates the number of times a particular instance of this entity (row) has been modified, starting from 1 (initial insertion).
Each time a managed object is modified, Z_OPT value in its corresponding database row is incremented.
The store maintains cache (referred to as row cache in Core Data docs) of NSIncrementalStoreNode instances, each having a version property equal to Z_OPT value returned by previous SELECT or UPDATE SQL query on managed object's row.
When a managed object is returned from NSManagedObjectContext (e.g. by executing NSFetchRequest on it), MOC creates snapshot of this object which contains this version number.
When the object is modified or deleted, Core Data makes sure that it has not been modified or deleted outside the context by comparing versions of cached row and object snapshot. All of this happen when -save: is called on the context that the object belongs to. If the versions are different then a merge conflict is detected and handled based on set merging policy.
When MOC is being saved, the -newValuesForObjectWithID:withContext:error: method is called for each modified/deleted object which in turn returns NSIncrementalStoreNode with version number. This version is then compared to snapshot's version and if they are different, the save fails with appropriate merge conflicts (at least with default merge policy).
This simple use case works properly with my store since -newValuesForObjectWithID:withContext:error: checks the row cache first which is enough if the object was concurrently modified in other context using the same store instance. If this is the case, then the cache contains updated row with higher version number which is enough to detect a conflict.
But how can I detect than the underlying database has been modified outside my store, possibly by other application or other store instance using the same database file? I know this is an unfrequent edge case but Core Data handles it properly and I would prefer to do the same.
Core Data's store uses SQL queries like these to update/delete object's row:
UPDATE ZFOO SET Z_OPT=Y, (...) WHERE (...) AND Z_OPT=X
DELETE FROM ZFOO WHERE (...) AND Z_OPT=X
where:
X - version number last known to the store (from cache)
Y - new version number
If such a query fails (no rows affected) the row is updated in store's cache and its version compared against the one previously cached.
My question is: how can a custom NSIncrementalStore inform Core Data that optimistic locking failure has occurred for some updated/deleted/locked objects? It is only the store that is able to tell that when it handles NSSaveChangesRequest passed to it its -executeRequest:withContext:error: method.
If the underlying database does not change under the store, then conflicts are detected since Core Data calls -newValuesForObjectWithID:withContext:error: on each modified/deleted/locked object prior to executing save changes request on the store. I was not able to find any way for NSIncrementalStore to inform Core Data that an optimistic locking failure has occurred after it started to handle the save request. Is there some undocumented way to do that? Core Data seems to throw some exception in that case which is then magically translated into failed save request with NSError listing all the conflicts. I am only able to mimic that partly by returning nil from -executeRequest:withContext:error: and creating the error message by myself. I think there must be a way to use the standard Core Data conflict handling mechanism in this scenario as well.
I realize that this is not an answer to you question, but I will try and give you my point of view on CoreData and correlation to Databases:
(1st level cache)
NSPesistentStoreCoordinator + NSPersistentStore == A single connection to the database
(2nd level cache)
NSManagedObjectContext == cache over the connection holding changes
So, to my understanding your issue is that you have multiple connections to your store, each making changes, but you have no central version control over your records.
Your store will receive a -executeRequest:withContext:error: with NSSaveRequestType
You will then be responsible to verify that the record versions match, if you find a conflict in the connection level (level 1) you report version mismatch between the context (level 2) and the coordinator.
you need to report version missmatch between your connection (level 1) and your store.
To be able to do this your store must report changes on it across all connections to it (ConnectionManager), or it might offer hooks to changes performed on it.
I'm no SQLite expert, but the SQLite API does have something to offer in that area:
update hook
commit hook
changes
total changes
(I have no experience in setting these kind of hooks, but if CoreData use them it will not show in the debug logs)
you can report these errors by setting the error pointer (NSError**) and setting its internal data to match the one that CoreData coordinator is setting (create merge conflict and set the information in them as needed)
Note that optimistic locking failure will only occur during -executeRequest:withContext:error:
(unless you have a rogue connection to the store, one that is not tracked by the manager.
To support this behaviour your manager might need to verify each record as it is committed for a save [huge performance cost] , or use some hooks into the changes recently made to records
)
To handle multiple connections to your store you might need to have a shared cache of NSIncrementalStoreNode, keyed by the store url:
static #{
url1 : actualCacheMapping1,
url2 : actualCacheMapping2,
...
}
each connection save to the store will be verified agains the store url actual cache.
Hope this make some sense for you.
My question is: how can a custom NSIncrementalStore inform Core Data that optimistic locking failure has occurred for some updated/deleted/locked objects? It is only the store that is able to tell that when it handles NSSaveChangesRequest passed to it its -executeRequest:withContext:error: method.
In an NSIncrementalStore, NSIncrementalStoreNodes represent the store snapshots. The version property of the node is the optimistic locking primitive. The persistent store is responsible for detecting optimistic locking failures in at the store level, while the managed object context can detect them higher up. An optimistic locking failure at the store level might happen if the system the store is talking to was changed by something else, and there is a conflict between that system's state and that representation of state in the persistent store. For example, if the store was communicating with a web service and the web service data was changed by another user, etc.
If an optimistic locking failure is detected in your store implementation during a save, your store is responsible for creating NSMergeConflict objects describing it. These will be propagated up by the NSPersistentStoreCoordinator.
[[NSMergeConflict alloc] initWithSource:managedObject newVersion:newVersion oldVersion:oldVersion cachedSnapshot:inMemorySnapshot persistedSnapshot:storedSnapshot];
Snapshot dictionaries should include all modelled attribute property names as keys along with their values. This does not include relationships. For some stores, using the values from the reference objects or NSIncrementalStoreNodes may suffice as long as they only include the modelled attribute property name as keys (and those are easy to get from the entity description).
Once these objects have been created, create an NSError in the NSCocoaErrorDomain with the code NSPersistentStoreSaveConflictsError. The userInfo object should contain the key NSPersistentStoreSaveConflictsErrorKey which should contain an array of the NSMergeConflict objects. Return that from the save request, and the NSPersistentStoreCoordinator will be responsible for finding resolution. Rememeber, you should not generate merge conflicts for conflicts between the state of objects in the NSManagedObjectContext and your store, only for conflicts between whatever in-memory or cached state in your store and where ever the data is kept or persisted (like a web service, or database, etc.)

Cross-Store weak relationship with Fetched Properties?

I would like to separate my reference data from my user data in my Core Data model to simplify future updates of my app (and because, I plan to store the database on the cloud and there is no need to store reference data on the cloud as this is part of my application). Therefore, I've been looking for a while for a way to code a cross-store relationship using fetched properties. I have not found any example implementations of this.
I have a Core Data model using 2 configurations :
data model config 1 : UserData (entities relative to user)
data model config 2 : ReferenceData (entities relative to application itself)
I set up 2 different SQLite persistent stores for both config.
UserData config (and store) contains entity "User"
ReferenceData config (and store) contains entities "Type" and "Item".
I would like to create two single-way weak relationships as below :
A "User" has a unique "Type"
A "User" has many "Items"
Here are my questions :
How do I set up my properties?
Do I need 2 properties for each relation (one for storing Unique ID and another to access my fetched results)?
Could this weak relationship be ordered?
Could someone give me an example implementation of this?
As a follow-on to Marcus' answer:
Looking through the forums and docs, I read that I should use the URI Representation of my entity instance instead of objectID. What is the reason behind this?
// Get the URI of my object to reference
NSURL * uriObjectB [[myObjectB objectID] URIRepresentation];
Next, I wonder, how do I store my object B URI (NSURL) in my parent object A as a weak relationship? What attribute type should I use? How do I convert this? I heard about archive... ?
Then, later I should retrieve the managed object the same way (by unconvert/unarchive the URIRepresentation) and get Object from URI
// Get the Object ID from the URI
NSManagedObjectID* idObjectB = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
// Get the Managed Object for the idOjectB ...
And last but not least, shouId I declare two properties in my entity A, one for persisting of URI needs and another for retrieving direclty object B?
NSURL * uriObjectB [objectA uriObjectB];
ObjectB * myObjectB = [objectA objectB];
As you can read, I really miss some simple example to implement thes weak relationships ! I would really appreciate some help.
Splitting the data is the right answer by far. Reference data should not be synced with the cloud, especially since iCloud has soft caps on what it will allow an application to sync and store in documents.
To create soft references across to stores (they do not need to be SQLite but it is a good idea for general app performance) you will need to have some kind of unique key that can be referenced from the other side; a good old fashioned foreign key.
From there you can create a fetched property in the model to reference the entity.
While this relationship cannot be ordered directly you can create order via a sort index or if it has a logical sort then you can sort it once you retrieve the data (I use convenience methods for this that return a sorted array instead of a set).
I can build up an example but you really are on the right track. The only fun part is migration. When you detect a migration situation you will need to migrate each store independently before you build up your core data stack. It sounds tricky but it really is not that hard to accomplish.
Example
Imagine you have a UserBar entity in the user store and a RefBar entity in the reference store. The RefBar will then have a fetchedProperty "relationship" with a UserBar thereby creating a ToOne relationship.
UserBar
----------
refBarID : NSInteger
RefBar
--------
identifier : NSInteger
You can then create a fetched property on the RefBar entity in the modeler with a predicate of:
$FETCHED_PROPERTY.refBarID == identifier
Lets name that predicate "userBarFetched"
Now that will return an array so we want to add a convenience method to the RefBar
#class UserBar;
#interface RefBar : NSManagedObject
- (UserBar*)userBar;
#end
#implementation RefBar
- (UserBar*)userBar
{
NSArray *fetched = [self valueForKey:#"userBarFetched"];
return [fetched lastObject];
}
#end
To create a ToMany is the same except your convenience method would return an array and you would sort the array before returning it.
As Heath Borders mentioned, it is possible to add a sort to the NSFetchedProperty if you want but you must do it in code. Personally I have always found it wasteful and don't use that feature. It might be more useful if I could set the sort in the modeler.
Using the ObjectID
I do not recommend using the ObjectID or the URIRepresentation. The ObjectID (and therefore the URIRepresentation of that ObjectID) can and will change. Whenever you migrate a database that value will change. You are far better off creating a non-changing GUID.
The weak relationship
You only need a single value on the M side of the relationship and that stores the foreign identifier. In your object subclass you only need to implement accessors that retrieve the object (or objects).
I would go with just one store.
For storing stuff in the cloud, you will anyway have to serialize the data, either as JSON or SQL statements, or whatever scheme you prefer.
You will need a local copy of the data on the user's device, so he can access it quickly and offline. The cloud store can have only the user entity, while the local store (part of the app) can also have the reference entity.
I have a similar project with a huge reference store (20000 records) with geographic information, and user generated content ("posts"). I use a single store. When I ship the app, the "posts" entity is also defined but empty. When I update the data model I simply re-generate the whole reference store before shipping.
I see absolutely no reason to go for a cross store solution here.

Resources