Les données de base « Upsert » de la base de données SQLite
-
22-09-2019 - |
Question
Je suis en train d'écrire une application qui a besoin de la possibilité de modifier et de persister plusieurs morceaux de données. J'ai décidé d'utiliser les données de base à cet effet. Lorsque l'utilisateur ouvre l'application pour la première fois que je besoin d'importer une grande quantité de données à partir d'une base de données SQLite, ces données se compose des nombreux à plusieurs.
Je voudrais trouver la meilleure façon d'insérer toutes ces données dans mon magasin de données de code. En ce moment, je me sers d'un NSOperation à faire l'importation alors que reste de l'application reste active, de sorte que l'utilisateur peut faire d'autres choses, mais je voudrais l'importation se produise le plus rapidement possible si est accessible tout de suite l'ensemble App .
La méthode que j'utilise est maintenant d'utiliser un NSFetchRequest pour tenter de trouver l'entité associée dans la banque de données, si l'entité est là, j'ajouter simplement comme une relation, si l'entité est pas là, je crée une nouveau et l'ajouter comme une relation. Cela fonctionne, mais je pense que c'est probablement même pas proche de l'optimum.
Le code J'utilise maintenant:
- (void)importEntitiesIntoContext: (NSManagedObjectContext*)managedObjectContext
{
// Setup the database object
static NSString* const databaseName = @"DBName.sqlite";
NSString* databasePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: databaseName];
sqlite3* database;
// Open the database from the user's filessytem
if ( sqlite3_open_v2( [databasePath UTF8String], &database, SQLITE_OPEN_READONLY, NULL ) == SQLITE_OK )
{
// Setup the SQL Statement
NSString* sqlStatement = [NSString stringWithFormat: @"SELECT some_columns FROM SomeTable;"];
sqlite3_stmt* compiledStatement;
if ( sqlite3_prepare_v2( database, [sqlStatement UTF8String], -1, &compiledStatement, NULL ) == SQLITE_OK )
{
// Create objects to test for existence of exercises
NSPredicate* predicate = [NSPredicate predicateWithFormat: @"something == $SOME_NAME"];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName: @"SomeEntity"
inManagedObjectContext: managedObjectContext];
NSFetchRequest* fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity: entityDescription];
// Loop through the results and add them to the feeds array
while ( sqlite3_step( compiledStatement ) == SQLITE_ROW )
{
NSString* someName = [NSString stringWithCharsIfNotNull: (char*)sqlite3_column_text( compiledStatement, 1 )];
NSPredicate* localPredicate = [predicate predicateWithSubstitutionVariables:
[NSDictionary dictionaryWithObject: someName
forKey: @"SOME_NAME"]];
[fetchRequest setPredicate: localPredicate];
NSError* fetchError;
NSArray* array = [managedObjectContext executeFetchRequest: fetchRequest
error: &fetchError];
if ( array == nil )
{
// handle error
}
else if ( [array count] == 0 )
{
SomeEntity* entity =
[NSEntityDescription insertNewObjectForEntityForName: @"SomeEntity"
inManagedObjectContext: managedObjectContext];
entity.name = someName;
// **here I call a method that attempts to add the relationships(listed below)**
}
else
{
// Some entity already in store
}
}
}
else
{
NSLog( @"sqlStatement failed: %@", sqlStatement );
}
// Release the compiled statement from memory
sqlite3_finalize( compiledStatement );
}
// All the data has been imported into this temporary context, now save it
NSError *error = nil;
if ( ![managedObjectContext save: &error] )
{
NSLog( @"Unable to save %@ - %@", [error localizedDescription] );
}
}
Méthode d'ajouter les relations:
- (void)setRelationshipForEntity: (Entity*)entity
inManagedObjectContext: (NSManagedObjectContext*)managedObjectContext
usingDatabase: (sqlite3*)database
entityId: (NSNumber*)entityId
{
// Setup the SQL Statement and compile it for faster access
NSString* sqlStatement = [NSString stringWithFormat: @"SELECT Relationship.name FROM Relationship JOIN Entitys_Relationship ON Entitys_Relationship.id_Relationship = Relationship.id JOIN Entitys ON Entitys_Relationship.id_Entitys = Entitys.id WHERE Entitys.id = %d;", [entityId integerValue]];
sqlite3_stmt* compiledStatement;
if ( sqlite3_prepare_v2( database, [sqlStatement UTF8String], -1, &compiledStatement, NULL ) == SQLITE_OK )
{
// Create objects to test for existence of relationship
NSPredicate* predicate = [NSPredicate predicateWithFormat: @"relationshipName == $RELATIONSHIP_NAME"];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName: @"EntityRelationship"
inManagedObjectContext: managedObjectContext];
NSFetchRequest* fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity: entityDescription];
while ( sqlite3_step( compiledStatement ) == SQLITE_ROW )
{
NSString* relationshipName = [NSString stringWithCharsIfNotNull: (char*)sqlite3_column_text( compiledStatement, 0 )];
NSPredicate* localPredicate = [predicate predicateWithSubstitutionVariables:
[NSDictionary dictionaryWithObject: relationshipName
forKey: @"RELATIONSHIP_NAME"]];
[fetchRequest setPredicate: localPredicate];
NSError* fetchError;
NSArray* array = [managedObjectContext executeFetchRequest: fetchRequest
error: &fetchError];
if ( array == nil )
{
// handle error
}
else if ( [array count] == 0 )
{
EntityRelationship* entityRelationship =
[NSEntityDescription insertNewObjectForEntityForName: @"EntityRelationship"
inManagedObjectContext: managedObjectContext];
entityRelationship.relationshipName = relationshipName;
[entity addRelationshipObject: entityRelationship];
//NSLog( @"Inserted relationship named %@", relationshipName );
}
else
{
[entity addRelationship: [NSSet setWithArray: array]];
}
}
}
else
{
NSLog( @"slqStatement failed: %@", sqlStatement );
}
// Release the compiled statement from memory
sqlite3_finalize( compiledStatement );
}
La solution
Où se trouve la base de données d'origine vient?
En règle générale, vous convertir toutes vos données à des données de base au cours du développement, puis expédier l'application avec un magasin de base de données pré-remplie (pas besoin d'un utilisateur d'attendre une importation).
Autres conseils
Apple a quelques suggestions sur la façon d'optimiser les grandes importations dans un magasin de données de base:
- Désactiver le undo-manager lors de l'importation de lots
- Ne pas insérer enregistrement par enregistrement - Créer des lots de taille n (selon la taille du disque)
- Utilisez un pool autorelease local et vider après chaque lot
Voir documentation pour les détails.