10

En nuestra aplicación en desarrollo, estamos utilizando Core Data con una tienda de respaldo sqlite para almacenar nuestros datos. El modelo de objetos para nuestra aplicación es complejo. Además, la cantidad total de datos servidos por nuestra aplicación es demasiado grande para caber en un paquete de aplicaciones iOS (iPhone/iPad/iPod Touch). Debido al hecho de que nuestros usuarios, por lo general, solo están interesados ​​en un subconjunto de los datos, hemos dividido nuestros datos de tal manera que la aplicación se envía con un subconjunto (aunque, ~ 100 MB) de los objetos de datos en el paquete de aplicaciones. Nuestros usuarios tienen la opción de descargar objetos de datos adicionales (de tamaño ~ 5 MB a 100 MB) de nuestro servidor después de que paguen los contenidos adicionales a través de las compras en la aplicación de iTunes. Los archivos de datos incrementales (existentes en las tiendas de respaldo sqlite) utilizan la misma versión xcdatamodel que los datos que se envían con el paquete; hay cero cambios en el modelo de objetos. Los archivos de datos incrementales se descargan de nuestro servidor como archivos sqlite con gzip. No queremos inflar nuestro paquete de aplicaciones enviando los contenidos incrementales con la aplicación. Además, no queremos confiar en las consultas sobre el servicio web (debido al complejo modelo de datos). Hemos probado la descarga de los datos sqlite incrementales de nuestro servidor. Hemos podido agregar el almacén de datos descargado al persistentStoreCoordinator compartido de la aplicación. ¿Cuál es una forma eficiente de combinar dos almacenes persistentes de datos centrales de 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(); 
           }    
       } 
} 

Sin embargo, hay dos problemas con hacerlo de esta manera.

  1. recuperación de datos los resultados (por ejemplo, con NSFetchResultController) mostrará con los datos de la incrementalStoreURL añade al final de los datos de la defaultStoreURL.
  2. Algunos de los objetos están duplicados. Hay muchas entidades con datos de solo lectura en nuestro modelo de datos; estos se duplican cuando agregamos el segundo persistentStore al persistentStoreCoordinator.

Lo ideal sería que Core Data fusione los gráficos de objeto de las dos tiendas persistentes en una (no hay relaciones compartidas entre los datos de las dos tiendas en el momento de la descarga de datos). Además, nos gustaría eliminar los objetos duplicados. Al buscar en la Web, vimos un par de preguntas de personas que intentan hacer lo mismo que nosotros, como this answer y this answer. Hemos leído Marcus Zarra's blog on importing large data sets in Core Data. Sin embargo, ninguna de las soluciones que hemos visto funcionó para nosotros. No queremos leer y guardar manualmente los datos de la tienda incremental en la tienda predeterminada, ya que creemos que esto será muy lento y propenso a errores en el teléfono. ¿Hay una manera más eficiente de hacer la fusión?

Hemos intentado resolver el problema implementando una migración manual de la siguiente manera. Sin embargo, no hemos podido lograr que la fusión suceda con éxito. No estamos muy claros sobre la solución sugerida por las respuestas 1 y 2 mencionadas anteriormente. El blog de Marcus Zarra abordó algunos de los problemas que teníamos al principio de nuestro proyecto al importar nuestro gran conjunto de datos en 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(); 
       } 
} 

Parece que el autor de respuesta 1 terminó la lectura de sus datos del almacén de incremento y guardar en el almacén predeterminado. Tal vez, hemos malentendido la solución sugerida por ambos artículos 1 & 2. El tamaño de nuestros datos puede impedirnos leer y volver a insertar manualmente nuestros datos incrementales en la tienda predeterminada. Mi pregunta es: ¿cuál es la forma más eficiente de obtener los gráficos de objetos de dos persistentStores (que tienen el mismo objectModel) para fusionarse en un persistentStore?

La migración automática funciona bastante bien cuando agregamos nuevos atributos de entidad a gráficos de objetos o modificamos relaciones. ¿Existe una solución simple para fusionar datos similares en la misma tienda persistente que será lo suficientemente resistente como para detenerse y reanudarse, ya que se realiza la migración automática?

+0

¿Dónde está Marcus Zarra cuando lo necesito? He avanzado un poco usando el método [NSPersistentStore migratePersistentStore: toURL: options: withType: error]. Solo necesito un código de limpieza más para llegar a donde debo estar. – Sunny

+0

Estoy luchando con lo mismo. ¿Puedes publicar lo que has propuesto hasta ahora? Estoy perdido. – damon

+0

¡Hecho! Déjame saber cómo resulta para ti. – Sunny

Respuesta

6

Después de varios intentos, he descubierto la manera de hacer este trabajo. El secreto es crear primero los datos de la tienda incremental sin ningún dato para las entidades de solo lectura. Sin dejar datos de solo lectura fuera de las tiendas incrementales, las instancias de entidades para estos se duplicarían después de la migración y fusión de datos. Por lo tanto, las tiendas incrementales se deben crear sin estas entidades de solo lectura. La tienda predeterminada será la única tienda que los tenga.

Por ejemplo, tenía entidades "País" y "Estado" en mi modelo de datos. Necesitaba tener solo una instancia de País y Estado en mi gráfico de objetos. Mantuve estas entidades fuera de las tiendas incrementales y las creé solo en la tienda predeterminada. Utilicé Fetched Properties para vincular libremente mi gráfico de objeto principal a estas entidades. Creé la tienda predeterminada con todas las instancias de entidad en mi modelo. Las tiendas incrementales o bien no tenían entidades de solo lectura (es decir, país y estado en mi caso) para comenzar o eliminarlas una vez completada la creación de datos.

El siguiente paso es agregar el almacenamiento incremental a su propio persistentStoreCoordinator (no es lo mismo que el coordinador de la tienda predeterminada a la que queremos migrar todos los contenidos) durante el inicio de la aplicación.

El último paso es llamar al método migratePersistentStore en la tienda incremental para fusionar sus datos al almacén principal (es decir, predeterminado). ¡Presto!

El siguiente fragmento de código ilustra los dos últimos pasos que mencioné anteriormente. Hice estos pasos para hacer que mi configuración combine datos incrementales en un almacén de datos principal para que funcionen.

{ 
    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

¿Puede comentar el rendimiento de esta solución para ayudar a cualquiera que esté considerando esta opción frente a la lectura y escritura manual de los datos de una tienda a otra? –

1

El motivo por el que su migración no funciona es porque el modelo de objeto administrado es idéntico.

Técnicamente, estás hablando de "migración de datos" y no de "migración de esquemas". La API de migración de CoreData está diseñada para la migración de esquemas, es decir, para gestionar los cambios en el modelo de objetos gestionados.

En lo que se refiere a la transferencia de datos de una tienda a otra, está por su cuenta. CoreData puede ayudarlo a ser eficiente mediante el uso de límites de lotes y recuperación en sus solicitudes de búsqueda, pero debe implementar la lógica usted mismo.

Parece que tiene dos tiendas persistentes, una grande y otra pequeña. Sería más eficiente cargar el pequeño y analizarlo, descubriendo el conjunto de claves primarias o identificadores únicos que necesita consultar en la tienda más grande.

A continuación, puede deducir fácilmente con solo consultar en la tienda más grande esos identificadores.

La documentación para NSFetchRequest tiene la API para la determinación del alcance sus consultas:

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

+0

Gracias por responder a mi pregunta. Técnicamente, la migración de datos básicos parece hacer más que la migración de esquemas. Mi pregunta es averiguar dónde está la línea y cómo puedo aprovechar lo que ya está allí para hacer el trabajo. Quiero evitar la fuerza bruta, ya que esto probablemente resultaría en un código difícil de mantener ya que Apple presenta más y más características. He avanzado un poco con el método [NSPersistentStore migratePersistentStore ::::]. Ya casi estoy allí. Deseo que alguien con experiencia haciendo lo que estoy tratando de hacer pueda aconsejarme. – Sunny

1

que no es necesario ningún migración - migración está diseñada para traer cambios en NSManagedObjectModel, no en los propios datos.

Lo que realmente necesita es un Coordinador de Tienda Pesado que administre dos Tiendas Persistentes. Eso es un poco complicado, pero no demasiado difícil, realmente.

Hay una pregunta similar, que puede explicarle lo que realmente necesita hacer. Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?

He aquí una buena arcticle Marcus Zarra

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

+0

Hola @Nikita, gracias por responder a mi pregunta. Creo que migrationManager hace más que migrar el esquema. Por lo tanto, sus datos se migran cuando agrega un atributo a su esquema y activa la migración automática. En cuanto a su comentario sobre el uso de un único persistentStoreCoordinator, eso es lo que estoy haciendo. Vea el fragmento de código que comienza en "if (incrementalStoreExists) {." Los enlaces que proporcionó no abordaron mi problema. Ya estoy usando múltiples tiendas persistentes y usando un solo coordinador para administrarlas. – Sunny

Cuestiones relacionadas