EDIT 1
While I understand that for this particular scenario (and other alike) I could use the mapping editor alone to migrate my store correctly so that the values in the persistent store don't jump around, but that's not a solution to my current problem but only avoids addressing the root of the problem. I am keen on sticking with my custom migration policy as this will give me a lot of control through the migration process, especially for future scenarious where setting up a custom migration policy will work for me. This is for a long term solution and not just for this scenario.
I urge you to try and help me solve the current situation at hand rather than diverting me to lightweight migration or advising me to avoid using a migration policy. Thank you.
I really do look forward to sorting this out and your valuable input/ideas on what I could do to fix this problem.
What I have done:
I have a migration policy set up so that the source data can be copied into the destination data from version 1 of the core model to version 2.
This is the migration policy:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error {
// Create the product managed object
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSString *productCode = [sInstance valueForKey:#"productCode"];
NSNumber *productPrice = [sInstance valueForKey:#"productPrice"];
[newObject setValue:productCode forKey:#"productCode"];
[newObject setValue:productPrice forKey:#"productPrice"];
//This is the field where the name has changed as well as the type.
[newObject setValue:[NSNumber numberWithBool:YES] forKey:#"productPriceNeedsUpdating"];
// Set up the association between the old source product and the new destination Product for the migration manager
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
/*
A test statement to make sure the destination object contains the correct
values int he right properties:
Product description: <NSManagedObject: 0xb983780> (entity: Product; id: 0xb9837b0 <x-coredata:///Product/t97685A9D-09B4-475F-BDE3-BC9176454AEF6> ; data: {
productCode = 9999;
productPrice = "2.09";
productPriceNeedsUpdating = 1;
})
*/
// Set up the association between the old source product and the new destination Product for the migration manager
return YES;
}
So even though the tested properties show the correct values in runtime, the resultant values saved in the data model store is incorrect as seen in the snapshots.
Here is a comparison from version 1 to version 2 of the data store.
Version 1: Correct
to Version 2: Which is now storing the values incorrectly.
The expected output should have the Product price inserted into the productPrice field and not in the ProductPriceNeedsUpdating field which should actually only have boolean values.
Can anyone help me understand what I am doing wrong, or explain whats happening here?
UPDATE 1 - Here are my entity mappings:
Update 2 - 20/aug/2014 01:02 GMT
When I remove the attribute ProductPriceLastUpdated of type date from version 1, and remove the attribute ProductPriceNeedsUpdate of type boolean in version 2, leaving only the two attributes that both match in version 1 and 2, then everything works. Even though I can leave it here and move on, I cant ignore the users that are currently using version 1 of the database which does have that pointless ProductPriceLastUpdated attribute which I need the type converted to boolean and also have the name changed to ProductPriceNeedsUpdate. Thats when things start going weird, and the price values are shown in the ProductPriceNeedsUpdate field instead of the productPrice field.
I hope someone can address the original problem and tell me why it is that the entityMapping, or more so, property mapping is not being saved properly?
Update 3 - EntityMapping and properties:
Version 1
Version 2
Any ideas?
First, if you want to just use a lightweight migration (which you really should in this case) you can get rid of your custom migration policy. In this instance, it's not needed. And, as a matter of fact, you can get rid of your custom mapping model as well. All you need to do is in your Version 2 model, select the productPriceNeedsUpdating boolean flag, and in the Attribute Detail inspector on the right, set the default value to YES. This will achieve the goal you're try to get to with your custom migration policy.
However, if you really need to write this in code with your custom migration policy, I would still not use custom code. You can achieve this migration with only a mapping model. Simply select the ProductToProduct mapping, and in the value expression for productNeedsUpdating, enter YES, or 1.
EDIT
So, after a rather lengthy screen share, it was determined that the migration was using code from Marcus Zarra's Core Data book describing progressively migrating stores. When this was written, WAL mode was not the default mode with Core Data. When WAL mode is enabled, progressively migrating stores don't function well since there are two more files to deal with, the Write Ahead Log, and the Shared Memory file. When simply replacing the old store with a new one, without first removing those files, odd things happen, such as described in this post. The solution ultimately ended up being to disable WAL mode for the progressively migrating scenario so the extra files are not generated in the first place.
Related
I am running into an issue with CoreData (using MagicalRecord) trying to change an attribute. I think this is the result of the object having relationships to two parent entities.
The object is a manual, this has a to-many relationship to both a car and library. The library contains all manual objects. A car has 1-3 manual items.
Every manual has a UID and the same object is shared between the car and library.
For some reason, once the object is set into the relationship for both, I cannot change the title (NSString) attribute of the manual.
I checked to make sure I am in the same context. Not sure what the issue is.
This is what I am logging:
NSLog(#"Manual Title: %#",manual.title);
//prints Old Manual
manual.title = #"New Manual"
NSLog(#"Manual Title: %#",manual.title);
//prints New Manual
I'm saving this inside a MagicalRecord saveUsingCurrentThreadContextWithBlockAndWait other unrelated NSManagedObjects in the same method are being saved.
When the app loads the data into the UI, it still reads "Old Manual"
Any suggestions?
Thank you for your time.
It turns out the issue was two-fold with the MagicalRecord methods I was using:
1) Instead of saveUsingCurrentThreadContextWithBlockAndWait I should have used saveWithBlockAndWait
2) When I was fetching the manual object, I wasn't passing the context, so I changed MR_findFirstWithPredicate to MR_findFirstWithPredicate:inContext
Hopefully this will save someone else some time
Edit 1
While I understand that for this particular scenario (and other alike) I could use the mapping editor alone to migrate my store correctly so that the values in the persistent store don't jump around, but that's not a solution to my current problem but only avoids addressing the root of the problem. I am keen on sticking with my custom migration policy as this will give me a lot of control through the migration process, especially for future scenarious where setting up a custom migration policy will work for me. This is for a long term solution and not just for this scenario.
I urge you to try and help me solve the current situation at hand rather than diverting me to lightweight migration or advising me to avoid using a migration policy. Thank you.
I really do look forward to sorting this out and your valuable input/ideas on what I could do to fix this problem.
What I have done:
I have a migration policy set up so that the source data can be copied into the new destination data from version 1 of the core model to version 2.
This is the migration policy:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error {
// Create the product managed object
Product *newProductInstance = [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSString *productCode = [sInstance valueForKey:#"productCode"];
NSNumber *productPrice = [sInstance valueForKey:#"productPrice"];
[newProductInstance setProductCode: productCode];
[newProductInstance setProductPrice:productPrice];
/**
The previous old product entries didnt have anything for the last attribute,
where as the new instances of product entity should have a default value of YES.
*/
[newProductInstance setProductPriceNeedsUpdating:[NSNumber numberWithBool:YES]];
/*
A test statement to make sure the destination object contains the correct
values int he right properties:
Product description: <NSManagedObject: 0xb983780> (entity: Product; id: 0xb9837b0 <x-coredata:///Product/t97685A9D-09B4-475F-BDE3-BC9176454AEF6> ; data: {
productCode = 9999;
productPrice = "2.09";
productPriceNeedsUpdating = 1;
})
*/
NSLog(#"Product description: %#", [newProductInstance description]);
// Set up the association between the old source product and the new destination Product for the migration manager
[manager associateSourceInstance:sInstance
withDestinationInstance:newProductInstance
forEntityMapping:mapping];
return YES;
}
So even though the tested properties show the correct values in runtime, the resultant values saved in the data model store is incorrect as seen in the snapshots.
Here is a comparison from version 1 to version 2 of the data store.
Version 1: Correct
to Version 2: Which is now storing the values incorrectly.
The expected output should have the Product price inserted into the productPrice field and not in the ProductPriceNeedsUpdating field which should actually only have boolean values.
Can anyone help me understand what I am doing wrong, or explain whats happening here?
UPDATE 1 - Here are my entity mappings:
Update 2 - 20/aug/2014 01:02 GMT
When I remove the attribute ProductPriceLastUpdated of type date from version 1, and remove the attribute ProductPriceNeedsUpdate of type boolean in version 2, leaving only the two attributes that both match in version 1 and 2, then everything works. Even though I can leave it here and move on, I cant ignore the users that are currently using version 1 of the database which does have that pointless ProductPriceLastUpdated attribute which I need the type converted to boolean and also have the name changed to ProductPriceNeedsUpdate. Thats when things start going weird, and the price values are shown in the ProductPriceNeedsUpdate field instead of the productPrice field.
I hope someone can address the original problem and tell me why it is that the entityMapping, or more so, property mapping is not being saved properly?
Update 3 - EntityMapping and properties:
Version 1
Version 2
For your scenario there is no need for a migration policy. You can just use the mapping model and do the following:
Map the non-changing entities to each other. In the mapping model editor it should say something like A productCode | $source.productCode. This is already filled in for you.
The attribute to be dropped (productPriceLastUpdated) should not appear at all because when you created the mapping model you specified the source and destination models. As the destination model does not contain this attribute, it won't show up.
Where the new boolean attribute productPriceNeedsUpdating is shown, enter '1' for the "Value Expression" (this is equivalent to #YES). It should look like this:
A productPriceNeedsUpdating | 1
Rather than define a policy, you can now just call
migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:
destinationType:destinationOptions:error
You can do very complex operations by just using the mapping model editor.
Your screen shots are from the model editor. Make sure you get familiar with the mapping editor.
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.
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.
I'm learning about concurrency in conjunction with EF4.0 and have a question about the locking pattern used.
Say I configure a fixed concurrency mode on a version number property.
Now say I fetch a record (entity) from the database (context) and edit some property. Version gets incremented and when SaveChanges is called on its context. If the current database (context) version matches the version of the original record (entity) the save continues, otherwise an OptimisticConcurrencyException gets thrown by EF.
Now, my point of interest is the following: between the check of the versions there's always a small period of time, however small, it is there. So in theory someone else could've just updated the record between the comparison and the actual save, thus possibly corrupting the data.
How does this get solved? It feels as if the problem just gets pushed forward.
There is no period of time between checking versions and updating record because the database command looks like:
UPDATE SomeTable
SET SomeColumn = 'SomeValue'
WHERE Id = #Id AND Version = #OldVersion
SELECT ##ROWCOUNT
The check and update is one atomic operation. Rowcount will return 0 if no record with Id = #Id and Version = #OldVersion exists and that zero is translated to the exception.
This can (and probably is) solved using locking hints.
For SQL Server, EF can query (SELECT) from the database WITH UPDLOCK.
This tells the Database Engine that, you want to read a/several records, and nobody else can change those records until you perform an update thereafter.
If you want to see this for yourself, check out the Sql Server Profiler which will show you the queries in real-time.
Hope that helps.
CAVEAT: I can't say for sure that this is the way EF handles this scenario because I haven't checked myself but, certainly if you were going to do it yourself, this is one way to do it.