iOS - Keep the old sqlite database while updating to new version - ios

I've found some other question but I didn't get any clear idea how to keep the data from old database while application update in ios.
Case 1:
Can I keep the old database?
if Case 1 is YES:
Can I insert new column or doing any changes in the old database and will it be safe?
if case 1 is NO:
Can I get the old database data in new database? Will the old database will be removed?
Case 2: If I give a different name to new data base (it'll be included in bundle)? If giving a new name keeps the old database can I delete the old database programatically?
What will be the best practice? Give a new name to database file for keeping the old one and then copy the old to to new database and delete the old database file? Just start using the old one?
Looking for help.. :)

Case 1: Can I keep the old database?
Yes, updating your application won't delete the database file stored in the documents directory. (But if it is in your bundle, it'll be removed)
if Case 1 is YES: Can I insert new column or doing any changes in the old database and will it be safe?
That'll depend on your implementation. You can use ALTER TABLE query for adding or removing column. You need to handle the new changes in your code, else it can cause problems.
Adding a column won't cause any issues in normal scenarios (Depends on your insert query statements and new field constraints)

It's fairly Case 1 as assuming you have copy your .sqlite file to document folder and when you update the application it will look for the database the can update its table and update its database without loosing anything.

If I understand your question (you're looking to update a model that's already deployed via the App Store), yes you can perform upgrades to an existing model using the .xcdatamodeld format in Xcode. The Core Data Versioning doc from Apple covers this topic comprehensively.
This can be a fiddly process, if you have precious user data stored in your model, you'll want to test this exhaustively before pushing out your updates.
To add a new version to your model;
Select your xcdatamodel file (e.g. model.xcdatamodel)
Click Editor > Add Model Version
Name the new model version (e.g. Model 2), based on Model 1
Open the Utilities pane
Select 'Model 2' for the Current Model Version
Then add this method to your controller class implementation file, to help make small changes to the data model (changing field types, adding columns etc).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Model.sqlite"];
NSError *error = nil;
// for performing minor changes to the coredata database
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Also, worth noting: Apple recommends you only store user relevant files in the Documents folder. SQLite database files and similar should be stored in /Library/Application Support.

Related

Remove core data model on iOS app update

My question is related to migration. I cannot do a lightweight migration as there are a lot of changes with attribute types and new relationships. I don't have time for a heavy weight migration since the code is not mine and needs faster delivery.
The workaround, which could work is when the app is upgraded, app should remove the old data and data model as the data is of no use and can be downloaded from the server again. On the app did finish launching, get the .db URL and just remove it and recreate it for the very first time after the upgrade?
After some research, all the methods are pointed to light weight migration. If there is a better way please assist.
-(void) removeCoreDataAndReset{
NSError *error;
NSPersistentStoreCoordinator *storeCoordinator = storeCordinator;
for (NSPersistentStore *store in storeCoordinator.persistentStores) {
[storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error];
}
// Initialise managedobjectcontext , Store Coordinator etc
}
Reinitialise all after this method as you do in statrt
To remove the persistent store, you need to remove:
The actual persistent store file. This is located wherever you put it. You tell Core Data where it is when you call addPersistentStoreWithType:configuration:URL:options:error:, so if you're not sure, check there.
The journal files. These will have the same name as the persistent store file, but with -wal and -shm added to the end. This is very important, because in most cases nearly all of the existing data is in these files.
You can remove files with methods on NSFileManager. If you do this, do it before accessing Core Data in any way, i.e. before creating any Core Data objects of any kind.

Core Data - mixing lightweight and custom migration

So I have version 1 of my Core Data app in the App Store and now I started working on version 2.
I have some small changes to my data base model, and some custom code I need to run after those changes to complete the upgrade from version 1 to version 2.
I can use Core Data's lightweight migration to deal with the model change, and I can run my custom code after the migration is done.
The problem is, i'm not sure what will happen in the future, when I'll build version 3,4,5...
suppose this is the case:
version 1 to version 2 - use lightweight migration
version 2 to version 3 - use custom migration with model mapping
version 3 to version 4 - use lightweight migration again
and so on..
I'm not sure how to build a mechanism that deal with this mix of lightweight and custom migrations.
I couldn't find any code online or in Core Data docs that talk about this issue, I mean this is a very common issue for most of the Core Data apps out there, is there any best practice examples for this issue?
Two methods are written below:
I personally like this method since it's just genius IMO, you can tell from the detail of my explanation why I'm so excited about this method. This method has been written in Marcus Zarra's core data second edition book. It's an implementation for an automatic Progressive migration process for both heavy and lightweight use.
A method I came up with for a possible implementation after the OP asked if there was a way to avoid having to use mapping models for each migration upgrade
Method 1
This is where Progressive Migration comes in. You create mapping models for each version upgrade that will deal between two model versions. A mapping model will tell the migration manager how to map an old version of the data store to the new version of the data store. So a mapping model requires a source and a destination.
You then have as many mapping models required that will cover all the version upgrade increments. So if you have 4 versions, youll have 3 mapping models that will aid the transition between each version upgrade. In the end youll have them named: V1toV2MappingModel, V2to3MappingModel, V3toV4MappingModel.
In each mapping model you can then toggle the options to let the migration manager know how you wish to migrate. Whether its via a custom policy or a normal copy migration. So you say that for v1 to v2 you require a light weight migration, simply select the appropriate mapping model, you then use the model/mapping editor to make the desired connections/mappings between old attributes to new attributes and if its a custom migration required, then go to the right mapping model, choose the product entity mapping that you wish to customise the migration for, then in the entitymapping inspector you'll see that you can apply a custom migration policy, just copy the name of your migration policy say for example MigrationPolicyV2toV3 since its that particular version that you wanted to have customised.
So in the image above you can see on the left hand side in the name of the mapping model that it's for Version 1 to Version 2. Notice that the attribute mapping for the ProductToProduct entity mapping is empty - in the middle. This is because if you look on the right of the image in the entity mapping inspector where it says Custom Policy, I've put the name of my migration policy. This then lets the migration manager know (during the migration process) how to map the attributes and values from the source to the destination - version 1 to version 2 - and it knows this because of the migration policy inputted. This also causes the value type to be changed to custom. letting you know its going to be a custom migration when it comes to the ProductToProduct entity mapping.
Then you have to define in your migration policy which will determine how you wish to copy the values over. This is where you do your custom stuff.
As you can see from the image above, its the custom migration policy I've set for the ProductToProduct entity mapping. You'll notice that I'm not actually doing anything custom, all this could have been done without the migration policy and could have been achieved by simply doing a copy migration (lightweight migration) by adjusting a few values in the entityMapping inspector and adjusting the Attribute mapping values in the image before. I only did all of this migration policy custom stuff just as an exercise so that I can learn and be prepared for the future just INCASE I ever needed to do a heavy migration. Better learn it now than later hey ;)
That's it for doing custom stuff. I suggest you read up on the following apple developer reference on NSEntityMigrationPolicy and the methods required to do more custom stuff so that you know how to have full control throughout the migration process whenever a particular revision upgrade requires some or full custom migration.
And for any custom/heavyweight migrations - where in my case I use a migration policy so that I can do some custom code stuff during the migration from V2 to V3 in my data store - then you create something called a 'migration policy' so that THAT mapping model will adhere to the custom migration rules your specify.
And you simply apply all the appropriate transitions/mappings for each mapping model so that the migration manager knows how to upgrade from one store to the next.
All you need then is some recursive code that will look at the existing store's meta data to determine whether it's compatible with the most current version, and if its not, it will then do a recursive migration for you, automatically, following the rules from each mapping model as it upgrades through the versions until the store is up to date with the current version.
So this way you can accommodate all users with any version and have them brought up to speed to the current version's store. So if a user is at version 1, it will recursively go from V1 to V2, then migrate to v3 all the way up to your current version. The same applies if the user has any other version.
This does mean that it will take a little longer in the migration process, but its a good way of migrating all your users no matter which version they have.
To find this progressive migration code, you need to read a book called Core Data 2nd Edition - Data storage and management for iOS, OS X, and iCloud, Chapter 3 Versioning and Migration sub chapter 3.6 Progressive Data Migration (An Academic Exercise) from pages 54 to 59.
He talks you through the code,and tells you step by step how to write the progressivelyMigrateURL:ofType:toModel:error: method. Once you've written the method with him, he then tells you how to call this method on application startup so that your users can have their stores automatically migrated progressively too.
So you should probably write the method first, then follow my steps up above or you can read through migration policies in the subchapters before.
I practically learned all this in the last 48 hours and have it all up and running now. Able to do lightweight migrations for some versions and then have custom migrations for my other versions all done automatically.
Method 2 - Another solution albeit more cumbersome IMO: You can always have lightweight migration setup bearing in mind that you apparently can achieve even complex situations with lightweight migration. But in the instances where heavy migration is required for a specific version upgrade, you can always do if statements where you can check the current store, and if and ONLY if the current version of the persistent store matches an upgrade where heavy migration is required, you then tell the migration manager to perform a heavy migration and to follow a mapping model with a migration policy for that instance only, and then resume lightweight migration if there are more version upgrades to do to get the persistent store to the most recent model version.
If you can refetch data from a server or anything like that, the best way to deal with this is by removing your old model and immediately recreating a new one with the current structure. This is embedded in a singleton class and is called every time a new managedobjectcontext has to be created. My code for this is the following:
- (NSManagedObjectContext *)managedObjectContext {
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:#"YOURDB.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
if (error.code == 134100) {
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
}
if ( ![[self managedObjectModel] isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if (![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] ) {
NSLog(#"*** Could not delete persistent store, %#", error);
abort();
} else {
[__persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeURL options: nil error: &error];
}
}
}
} else {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return __persistentStoreCoordinator;
}
The simplest way is keeping old attributes in new version and not using them anymore. if you need map them to new attributes, just do the mapping after lightweight DB upgrade. The world will keep peaceful.
The benefits:
no need to make mapping model.
keep using light weight upgrade.
Let me give you example:
let's say we have a entity named Student with attribute name in V1, and we gonna map it to a new attribute firstName in V2.
and we have a method as below, so then we can call this method after regular lightweight CoreData upgrade, enjoy!
- (void)migrateStudentRecords {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
NSArray *result = [defaultContext executeFetchRequest:fetchRequest error:error];
if (result && result.count > 0) {
[result enumerateObjectsUsingBlock:^(Student *_Nonnull newRecord, NSUInteger idx, BOOL * _Nonnull stop) {
newRecord.firstName = newRecord.name;
newRecord.name = nil;
}];
if (defaultContext.hasChanges) {
[defaultContext save:error];
}
}
}

How to add SQLite database to iOS (CoreData) app?

I have an iPhone app that uses CoreData.
I want to add a pre-populated SQLite database to it. The database has 1 table (geographic locations, about 50K of them).. cities.sql
I am a bit puzzled what would be the best way to add this database?
I saw several approaches (such as locating the app folder in /Users/user/Library/... ) but my external database does not really have the same structure as apps database (no "User" table etc..).
I just want to treat this cities.sqlite as some data source.. I don't mind merging it with the apps appname.sqlite if necessary...
I am also using RestKit to manage the CoreData / API integration.
Question - how do I add this cities.sqlite to the app so I can ship the app with the pre-populated data from that database ?
Ok,
my approach to create a pre-populated db is to create a dummy app that has the goal to only populate the db you want to create. This let me to do some testing on the db without using the real app. Obviously, models should be the same.
Then, you can put it to the main bundle of your real app. Once executed, the app will check if the db exists in your document folder (for example), if not, it will copy the db from the bundle to the document folder.
NSString *storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"yourStore.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"yourStore" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
In Core Data Import, you can find what I mean (for the second part, but you are free to follow also the approach to populate the db).
Hope that helps.
Edit
I'll try to explain better.
You create a dummy project.
Here you can use the model (with its entities) you created in the main app. Just copy it i your dummy project.
Create some code to populate the sql store through NSManagedObjectContext stuff.
The result will consist in a sql store already populated. You don't need to touch any sql store (only through Core Data).
Move to the application folder directory into the App Simulator, copy the store and put it in your main application bundle.
EDIT: If you are working with plain SQLite database, you will need to migrate the data to CoreData-friendly persistent store. To do this you can use sqlite library to read the data. You can do this in app directly, or you write some ulitity app for this. After you get the SQLite Core Data persistent store, follow my original post:
With Core Data you can have multiple SQLite stores combined into one context. Just add two persistent stores to your NSPersistentStoreCoordinator.
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:mainSQLiteURL // URL for your main DB
options:nil
error:nil];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:citiesSQLiteURL // URL for the additional DB
options:nil
error:nil];
After this, when you create NSFetchRequest with entity City (I don't know your entity name) it will return cities from both SQLite files.
In case you want to just search one of the stores, you can set -setAffectedStores: of your fetch request. Also, when you insert new City object, you will need to specify the persistent store by calling -assignObject:toPersistentStore: on your context. Otherwise it will get confused about where to save the new city.
Or just merge those two stores to a single file.

Adding a new entity to coredata - can I still use lightweight migration?

I have an existing core data set and I want to add an entity to it. I'm a little confused over whether I can use lightweight migration after adding a new entity to transition existing users to the new model.
The current modal is (just showing entities):
Story 1toMany-> Sentences
I need:
Story 1toMany-> Sentences 1toMany-> Media
Can I use the lightweight migration tool to do this?
I've read in the documentation:
For Core Data to be able to generate an inferred mapping model,
changes must fit an obvious migration pattern, for example:
Simple addition of a new attribute Removal of an attribute A
non-optional attribute becoming optional An optional attribute
becoming non-optional, and defining a default value Renaming an entity
or property
But this question seems to suggest lightweight migration will still work with the addition of an entity. Since the new media entity is optional I can't see how it would practically be a problem.
Yes, you will likely be able to use lightweight migration. In my experience, I've found you'll want to Add Model Version... under the Editor menu before you make changes to your CoreData Model. This way there is a before and after scenario to map. Then, you'll need to set the new model as the current model. (You can now add an entity to the Core Data model. Be sure that you are working on the correct model.)
Finally, you need to make sure you pass options for initializing the PersistentStoreCoordinator.
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool: YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {...
Check the core data video "Mastering core data" from wwdc 2010. They talk about migration for your specific case. Long story short: yes, you can use lightweight migration. Just pass the options dictionary when initializing the NSPersistentStoreCoordinator instance:
NSDictionary *dictionary=[NSDictionary dictionaryWithObjects:#[ [NSNumber numberWithBool:YES], [NSNumber numberWithBool:YES]] forKeys:#[ NSMigratePersistentStoresAutomaticallyOption, NSInferMappingModelAutomaticallyOption]];

Adding Entity to Core Data

We have an app that uses Core Data.
In the next version I would like to add a new Entity to the already existing Entities.
Is ok just to add the new one and then populate it from the software, or is there something that I have to think about?
There's a couple of types of migration. The easiest is lightweight migration with an inferred mapping model — what that means is that you just tell it to do a migration and the software handles the rest. The caveat, however, is that it can only cope with certain kinds of changes. Adding an entity should be OK.
To enable lightweight migration, you need to pass in a few options when opening your persistent store:
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
The one final thing to do is when making the change to your data model, you need to add a model version. In Xcode 4, select your data model in the sidebar, choose Add Model Version from the Editor menu, and name your new version. Then you need to set the new version as the active one: choose your main data model file from the left sidebar again, and then in the right sidebar, first tab, there should be a "Versioned Data Model" popup menu.
This is very important. To do a migration, Core Data needs the version of your model that the old store was created with, as well as the version you want to migrate to. If you don't have the old version, migration will fail.
Changing the model is a difficult task. In theory it should be easy to just add an entity, but Apple built in a migration tool for the task. (This is in case you change the structure of existing data)
I have migrated Core Data databases before, but on MAC OS and it was over 2 years ago.
Here is the guide
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreDataVersioning/Introduction/Introduction.html

Resources