10

Nella nostra app in fase di sviluppo utilizziamo i dati di base con un backing store SQL per archiviare i nostri dati. Il modello a oggetti per la nostra app è complesso. Inoltre, la quantità totale di dati offerti dalla nostra app è troppo grande per rientrare in un pacchetto di app iOS (iPhone/iPad/iPod Touch). Poiché i nostri utenti sono, in genere, interessati solo a un sottoinsieme dei dati, abbiamo suddiviso i nostri dati in modo tale che l'app sia fornita con un sottoinsieme (anche se ~ 100 MB) degli oggetti dati nel pacchetto di app. I nostri utenti hanno la possibilità di scaricare oggetti dati aggiuntivi (di dimensioni ~ 5 MB a 100 MB) dal nostro server dopo che pagano per i contenuti aggiuntivi tramite acquisti in-app di iTunes. I file di dati incrementali (esistenti nei backing backup di tipo sqlite) utilizzano la stessa versione xcdatamodel dei dati forniti con il pacchetto; non vi sono modifiche al modello dell'oggetto. I file di dati incrementali vengono scaricati dal nostro server come file sqlite con gzip. Non vogliamo gonfiare il nostro pacchetto di app spedendo i contenuti incrementali con l'app. Inoltre, non vogliamo fare affidamento su query su webservice (a causa del complesso modello di dati). Abbiamo testato il download dei dati sqlite incrementali dal nostro server. Siamo stati in grado di aggiungere l'archivio dati scaricato al persistentStoreCoordinator condiviso dell'app. Che cos'è un modo efficace per unire due archivi persistenti di dati core iOS?

{ 
       NSError *error = nil; 
       NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 
                                [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
                                [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 

       if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error]) 
       {            
           NSLog(@"Failed with error:  %@", [error localizedDescription]); 
           abort(); 
       }    

       // Check for the existence of incrementalStore 
       // Add incrementalStore 
       if (incrementalStoreExists) { 
           if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error]) 
           {            
               NSLog(@"Add of incrementalStore failed with error:  %@", [error localizedDescription]); 
               abort(); 
           }    
       } 
} 

Tuttavia, ci sono due problemi con facendo in questo modo.

  1. dati recuperare i risultati (ad esempio, con NSFetchResultController) presentarsi con i dati dal incrementalStoreURL aggiunto alla fine dei dati dal defaultStoreURL.
  2. Alcuni degli oggetti sono duplicati. Ci sono molte entità con i dati di sola lettura nel nostro modello di dati; questi vengono duplicati quando aggiungiamo il secondo persistentStore a persistentStoreCoordinator.

Idealmente, vorremmo Core Data di unire i grafi di oggetti da due negozi persistenti in uno (non ci sono relazioni condivise tra i dati dei due negozi al momento del download di dati). Inoltre, vorremmo rimuovere gli oggetti duplicati. Cercando sul web, abbiamo visto un paio di domande da parte di persone che tentavano di fare la stessa cosa che stiamo facendo, come ad esempio this answer e this answer. Abbiamo letto Marcus Zarra's blog on importing large data sets in Core Data. Tuttavia, nessuna delle soluzioni che abbiamo visto ha funzionato per noi. Non vogliamo leggere e salvare manualmente i dati dall'archivio incrementale all'archivio predefinito poiché pensiamo che questo sarà molto lento e soggetto a errori sul telefono. C'è un modo più efficiente di fare l'unione?

Abbiamo tentato di risolvere il problema implementando una migrazione manuale come segue. Tuttavia, non siamo stati in grado di ottenere con successo l'unione. Non siamo molto chiari sulla soluzione suggerita dalle risposte 1 e 2 di cui sopra. Il blog di Marcus Zarra ha affrontato alcuni dei problemi che avevamo all'inizio del nostro progetto importando il nostro grande set di dati in iOS.

{ 
       NSError *error = nil; 
       NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 
                                [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
                                [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];        

       NSMigrationManager *migrator = [[NSMigrationManager alloc] initWithSourceModel:__managedObjectModel destinationModel:__managedObjectModel]; 
       if (![migrator migrateStoreFromURL:stateStoreURL 
                                type:NSSQLiteStoreType 
                             options:options 
                    withMappingModel:nil 
                    toDestinationURL:destinationStoreURL 
                     destinationType:NSSQLiteStoreType 
                  destinationOptions:nil 
                               error:&error]) 
       { 
           NSLog(@"%@", [error userInfo]); 
           abort(); 
       } 
} 

Sembra che l'autore di risposta 1 ha finito per leggere i suoi dati dall'archivio incrementale e il salvataggio al negozio di default. Forse abbiamo frainteso la soluzione suggerita da entrambi gli articoli 1 & 2. La dimensione dei nostri dati potrebbe impedirci di leggere e reinserire manualmente i nostri dati incrementali nell'archivio predefinito. La mia domanda è: qual è il modo più efficiente per ottenere i grafici degli oggetti da due persistentStores (che hanno lo stesso objectModel) per unire in un unico persistentStore?

La migrazione automatica funziona piuttosto bene quando si aggiungono nuovi attributi di entità ai grafici degli oggetti o si modificano le relazioni. Esiste una soluzione semplice per unire dati simili nello stesso archivio persistente che sarà abbastanza flessibile da fermarsi e riprendere - con la migrazione automatica?

+0

Dove è Marcus Zarra quando ho bisogno di lui? Ho fatto qualche progresso usando il metodo [NSPersistentStore migratePersistentStore: toURL: options: withType: error]. Ho solo bisogno di un po 'di codice di pulizia per arrivare dove devo essere. – Sunny

+0

Sto litigando con la stessa cosa. Puoi pubblicare ciò che hai inventato finora? Mi sono perso. – damon

+0

Fatto! Fammi sapere come va a finire per te. – Sunny

risposta

6

Dopo vari tentativi, ho capito come fare questo lavoro. Il segreto è innanzitutto creare i dati del negozio incrementale senza dati per le entità di sola lettura. Senza lasciare dati di sola lettura dagli archivi incrementali, le istanze delle entità per questi verrebbero duplicate dopo la migrazione e l'unione dei dati. Quindi, i depositi incrementali dovrebbero essere creati senza queste entità di sola lettura. L'archivio predefinito sarà l'unico negozio che li ha.

Ad esempio, nel mio modello di dati sono presenti entità "Paese" e "Stato". Avevo bisogno di avere una sola istanza di Paese e Stato nel mio grafico degli oggetti. Ho tenuto queste entità fuori da archivi incrementali e li ho creati solo nell'archivio predefinito. Ho usato Fetched Properties per collegare liberamente il mio oggetto principale grafico a queste entità. Ho creato l'archivio predefinito con tutte le istanze di entità nel mio modello. I magazzini incrementali o non avevano le entità di sola lettura (ad esempio, Paese e Stato nel mio caso) per iniziare o cancellarli dopo che la creazione dei dati è stata completata.

Il passaggio successivo consiste nell'aggiungere l'archivio incrementale al proprio persistentStoreCoordinator (non lo stesso del coordinatore per l'archivio predefinito su cui vogliamo migrare tutti i contenuti) durante l'avvio dell'applicazione.

La fase finale è chiamare il metodo migratePersistentStore sull'archivio incrementale per unire i dati alla principale (cioè, default) negozio. Presto!

Il seguente frammento di codice illustra gli ultimi due passaggi sopra menzionati. Ho fatto questi passaggi per rendere il mio setup per unire i dati incrementali in un data store principale per funzionare.

{ 
    NSError *error = nil; 
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, 
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error]) 
    {    
     NSLog(@"Failed with error: %@", [error localizedDescription]); 
     abort(); 
    }  

    // Check for the existence of incrementalStore 
    // Add incrementalStore 
    if (incrementalStoreExists) { 

     NSPersistentStore *incrementalStore = [_incrementalPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error]; 
     if (!incrementalStore) 
     { 
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
      abort(); 
     }  

     if (![_incrementalPersistentStoreCoordinator migratePersistentStore:incrementalStore 
      toURL:_defaultStoreURL 
      options:options 
      withType:NSSQLiteStoreType 
      error:&error]) 
     { 
      NSLog(@"%@", [error userInfo]); 
      abort(); 

     } 

     // Destroy the store and store coordinator for the incremental store 
     [_incrementalPersistentStoreCoordinator removePersistentStore:incrementalStore error:&error]; 
     incrementalPersistentStoreCoordinator = nil; 
     // Should probably delete the URL from file system as well 
     // 
    } 
} 
+0

Puoi commentare le prestazioni di questa soluzione per aiutare chiunque stia considerando questa opzione rispetto a leggere e scrivere manualmente i dati da un negozio all'altro? –

1

Il motivo per cui la migrazione non funziona è perché il modello oggetto gestito è identico.

Tecnicamente, si parla di "migrazione dei dati" e non di "migrazione dello schema". L'API di migrazione di CoreData è progettata per la migrazione dello schema, ovvero la gestione delle modifiche al modello a oggetti gestito.

Per quanto riguarda il trasferimento di dati da un negozio a un altro, sei come se fossi da solo. CoreData può aiutarti a essere efficiente utilizzando i limiti di batching e fetch delle tue richieste di recupero, ma devi implementare la logica da solo.

Sembra che tu abbia due negozi persistenti, uno grande e uno piccolo. Sarebbe molto più efficiente caricare il più piccolo e analizzarlo, scoprendo l'insieme di chiavi primarie o identificatori univoci che è necessario interrogare nel grande magazzino.

Si potrebbe quindi semplificare l'operazione semplicemente interrogando il più grande archivio per quegli identificatori.

La documentazione per NSFetchRequest ha l'API per scoping le vostre domande:

https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html

+0

Grazie per aver risposto alla mia domanda. Tecnicamente, la migrazione dei dati di base sembra fare di più della migrazione dello schema. La mia domanda è cercare di capire dove si trova la linea e come posso sfruttare ciò che è già lì per fare il lavoro. Voglio evitare la forza bruta, in quanto ciò potrebbe comportare il difficile mantenimento del codice mentre Apple introduce sempre più funzionalità. Ho fatto qualche progresso usando il metodo [NSPersistentStore migratePersistentStore ::::]. Ci sono quasi. Vorrei che qualcuno con esperienza facendo ciò che sto cercando di fare potesse consigliarmi. – Sunny

1

non hai bisogno di alcuna migrazione - migrazione è progettato per portare cambiamenti in NSManagedObjectModel, non in dati stessi.

Ciò di cui si ha realmente bisogno è un coordinatore di negozio Pesistent che gestisca due magazzini permanenti. È un po 'complicato, ma non troppo difficile, davvero.

C'è una domanda simile, che può spiegarti, cosa devi veramente fare. Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?

Ecco un buon arcticle da Marcus Zarra

http://www.cimgf.com/2009/05/03/core-data-and-plug-ins/

+0

Ciao @Nikita, grazie per aver risposto alla mia domanda. Credo che migrationManager faccia più della migrazione dello schema. Pertanto, i dati vengono migrati quando si aggiunge un attributo allo schema e si attiva la migrazione automatica. Per quanto riguarda il tuo commento sull'utilizzo di un singolo persistentStoreCoordinator, è quello che sto facendo. Vedere il frammento di codice che inizia con "if (incrementalStoreExists) {." I collegamenti che hai fornito non hanno risolto il mio problema. Sto già utilizzando più archivi persistenti e utilizzo di un singolo coordinatore per gestirli. – Sunny

Problemi correlati