web-dev-qa-db-fra.com

Exemple ou explication de la migration des données de base avec plusieurs passes?

Mon application iPhone doit migrer son magasin de données de base et certaines bases de données sont assez volumineuses. La documentation d'Apple suggère d'utiliser "plusieurs passes" pour migrer les données afin de réduire l'utilisation de la mémoire. Cependant, la documentation est très limitée et n'explique pas très bien comment procéder. Quelqu'un peut-il me pointer vers un bon exemple, ou expliquer en détail le processus de suppression?

84
Jason

J'ai compris ce que Apple astuces dans leur documentation . C'est en fait très facile mais un long chemin à parcourir avant qu'il ne soit évident. Je vais illustrer l'explication avec un La situation initiale est la suivante:

Version 1 du modèle de données

enter image description hereenter image description here

C'est le modèle que vous obtenez lorsque vous créez un projet avec le modèle "Application basée sur la navigation avec stockage de données de base". Je l'ai compilé et j'ai fait quelques frappes avec l'aide d'une boucle for pour créer environ 2k entrées, toutes avec des valeurs différentes. Là, nous allons 2.000 événements avec une valeur NSDate.

Maintenant, nous ajoutons une deuxième version du modèle de données, qui ressemble à ceci:

enter image description here

Modèle de données version 2

La différence est la suivante: l'entité Event a disparu et nous en avons deux nouvelles. Un qui stocke un horodatage en tant que double et le second qui devrait stocker une date en tant que NSString.

L'objectif est de transférer tous les événements de la version 1 vers les deux nouvelles entités et de convertir les valeurs tout au long de la migration. Cela se traduit par deux fois les valeurs chacune comme un type différent dans une entité distincte.

Pour migrer, nous choisissons la migration à la main et nous le faisons avec des modèles de cartographie. C'est également la première partie de la réponse à votre question. Nous effectuerons la migration en deux étapes, car la migration des entrées 2k prend beaucoup de temps et nous aimons garder une empreinte mémoire faible.

Vous pouvez même aller de l'avant et diviser ces modèles de mappage pour migrer uniquement les plages des entités. Supposons que nous ayons un million d'enregistrements, cela peut bloquer tout le processus. Il est possible de réduire les entités récupérées avec un prédicat de filtre .

Retour à nos deux modèles de cartographie.

Nous créons le premier modèle de cartographie comme celui-ci:

1. Nouveau fichier -> Ressource -> Modèle de mappage enter image description here

2. Choisissez un nom, j'ai choisi StepOne

3. Définir le modèle de données source et destination

enter image description here

Première étape du modèle de mappage

enter image description here

enter image description here

enter image description here

La migration en plusieurs passes n'a pas besoin de politiques de migration d'entité personnalisées, mais nous le ferons pour obtenir un peu plus de détails pour cet exemple. Nous ajoutons donc une stratégie personnalisée à l'entité. Il s'agit toujours d'une sous-classe de NSEntityMigrationPolicy .

enter image description here

Cette classe de règles implémente certaines méthodes pour réaliser notre migration. Cependant, c'est simple dans ce cas, nous devrons donc implémenter une seule méthode: createDestinationInstancesForSourceInstance:entityMapping:manager:error: .

L'implémentation ressemblera à ceci:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

Dernière étape: la migration elle-même

Je vais sauter la partie pour configurer le deuxième modèle de mappage qui est presque identique, juste un timeIntervalSince1970 utilisé pour convertir le NSDate en double.

Enfin, nous devons déclencher la migration. Je vais sauter le code passe-partout pour l'instant. Si vous en avez besoin, je posterai ici. Il peut être trouvé à Personnalisation du processus de migration c'est juste une fusion des deux premiers exemples de code. La troisième et dernière partie sera modifiée comme suit: Au lieu d'utiliser la méthode de classe de la classe NSMappingModelmappingModelFromBundles:forSourceModel:destinationModel: nous utiliserons le initWithContentsOfURL: car la méthode de classe ne renverra qu'un seul, peut-être le premier, modèle de mappage trouvé dans le bundle.

Nous avons maintenant les deux modèles de mappage qui peuvent être utilisés à chaque passage de la boucle et envoyer la méthode de migration au gestionnaire de migration. C'est ça.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

Notes

  • Un modèle de mappage se termine par cdm dans le bundle.

  • Le magasin de destination doit être fourni et ne doit pas être le magasin source. Vous pouvez, après une migration réussie, supprimer l'ancien et renommer le nouveau.

  • J'ai apporté quelques modifications au modèle de données après la création des modèles de mappage, ce qui a entraîné des erreurs de compatibilité, que je n'ai pu résoudre qu'avec la recréation des modèles de mappage.

171
Nick Weaver

Ces questions sont liées:

Problèmes de mémoire lors de la migration de grandes banques de données CoreData sur iPhone

Migration de données de base en plusieurs passes en morceaux avec iOS

Pour citer le premier lien:

Ceci est discuté dans la documentation officielle dans la section "Passes multiples", mais il semble que leur approche suggérée consiste à diviser votre migration par type d'entité, c'est-à-dire à créer plusieurs modèles de mappage, chacun migrant un sous-ensemble des types d'entité de la modèle de données complet.

3
occulus