10

W naszej opracowywanej aplikacji używamy Core Data ze sklepem baz danych sqlite do przechowywania naszych danych. Model obiektowy naszej aplikacji jest złożony. Łączna ilość danych obsługiwanych przez naszą aplikację jest zbyt duża, aby zmieścić się w pakiecie aplikacji na iOS (iPhone/iPad/iPod Touch). Ze względu na to, że nasi użytkownicy są zazwyczaj zainteresowani tylko podzbiorem danych, podzieliliśmy nasze dane w taki sposób, aby aplikacja zawierała podzbiór (aczkolwiek ~ 100 MB) obiektów danych w pakiet aplikacji. Nasi użytkownicy mają możliwość pobrania dodatkowych obiektów danych (o wielkości ~ 5 MB do 100 MB) z naszego serwera po opłaceniu dodatkowej zawartości za pośrednictwem zakupów w aplikacji iTunes. Przyrostowe pliki danych (istniejące w magazynach sqlite) używają tej samej wersji xcdatamodel, co dane dostarczane z pakietem; są zerowe zmiany w modelu obiektowym. Przyrostowe pliki danych są pobierane z naszego serwera jako skompresowane pliki sqlite z gzipem. Nie chcemy powiększać pakietu aplikacji, wysyłając przyrostową zawartość do aplikacji. Ponadto nie chcemy polegać na zapytaniach dotyczących usługi sieciowej (ze względu na złożony model danych). Testowaliśmy pobieranie przyrostowych danych sqlite z naszego serwera. Mogliśmy dodać pobrany magazyn danych do współużytkowanego persistentStoreCoordinator aplikacji. Jaki jest skuteczny sposób scalania dwóch trwałych składnic danych podstawowych systemu 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(); 
           }    
       } 
} 

Istnieją jednak dwa problemy robi to w ten sposób.

  1. wyników pobierania danych (na przykład z NSFetchResultController) wykazują z danych z incrementalStoreURL przyłączonej do końca danych z defaultStoreURL.
  2. Niektóre z obiektów są duplikowane. W naszym modelu danych istnieje wiele podmiotów o danych tylko do odczytu; zostaną one zduplikowane, gdy dodamy drugi obiekt persistentStore do persistentStoreCoordinator.

Idealnie chcielibyśmy, aby Core Data scalała wykresy obiektów z dwóch trwałych sklepów w jeden (nie ma wspólnych relacji między danymi z dwóch sklepów w momencie pobierania danych). Ponadto chcielibyśmy usunąć zduplikowane obiekty. Przeszukując internet, zobaczyliśmy kilka pytań od osób próbujących zrobić to samo, co robimy - takie jak this answer i this answer. Przeczytaliśmy Marcus Zarra's blog on importing large data sets in Core Data. Jednak żadne z rozwiązań, które widzieliśmy, nie działało dla nas. Nie chcemy ręcznie odczytywać i zapisywać danych z magazynu przyrostowego do domyślnego sklepu, ponieważ uważamy, że będzie to bardzo powolne i narażone na błędy w telefonie. Czy istnieje skuteczniejszy sposób na scalenie?

Próbowaliśmy rozwiązać problem, wprowadzając ręczną migrację w następujący sposób. Jednak nie udało nam się uzyskać scalenia. Nie jesteśmy do końca przekonani co do rozwiązania sugerowanego przez odpowiedzi 1 i 2, o których mowa powyżej. Blog Marcusa Zarry omówił niektóre z problemów, które mieliśmy na początku naszego projektu, importując nasz duży zestaw danych do 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(); 
       } 
} 

Wydaje się, że autor odpowiedzi 1 zakończył się czytając jego dane z bieżących oszczędności do sklepu i magazynu domyślnego. Być może, źle zrozumieliśmy rozwiązanie sugerowane przez oba artykuły 1 & 2. Rozmiar naszych danych może uniemożliwić nam ręczne odczytywanie i ponowne wstawianie naszych danych przyrostowych do domyślnego sklepu. Moje pytanie brzmi: jaki jest najskuteczniejszy sposób na uzyskanie wykresów obiektów z dwóch trwałych obiektów (które mają ten sam obiekt), aby scalić się w jeden plik persistentStore?

Automatyczne migrowanie działa całkiem dobrze, gdy dodajemy nowe atrybuty encji do wykresów obiektów lub modyfikujemy relacje. Czy istnieje proste rozwiązanie polegające na łączeniu podobnych danych w ten sam trwały sklep, który będzie wystarczająco odporny na zatrzymanie i wznowienie - w miarę jak przeprowadzana jest automatyczna migracja?

+0

Gdzie jest Marcus Zarra, kiedy go potrzebuję? Poczyniono pewne postępy przy użyciu metody [NSPersistentStore migratePersistentStore: toURL: options: withType: error]. Potrzebuję tylko kilku dodatkowych kodów, żeby dostać się tam, gdzie powinienem być. – Sunny

+0

Walczę z tym samym. Czy możesz napisać, co do tej pory wymyśliłeś? Zgubiłem się. – damon

+0

Gotowe! Daj mi znać, jak ci się wydaje. – Sunny

Odpowiedz

6

Po kilku próbach, ja zorientowali się, jak do tej pracy. Sekret polega na tym, aby najpierw utworzyć przyrostowe dane sklepu bez żadnych danych dla jednostek tylko do odczytu. Bez pozostawiania danych tylko do odczytu z magazynów przyrostowych wystąpienia instancji dla nich zostaną zduplikowane po migracji danych i scaleniu. W związku z tym magazyny przyrostowe powinny być tworzone bez tych jednostek tylko do odczytu. Domyślny sklep będzie jedynym sklepem, który je posiada.

Na przykład w moim modelu danych posiadałem jednostki "Kraj" i "Stan". Musiałem mieć tylko jedno wystąpienie kraju i stanu na wykresie obiektu. Trzymałem te jednostki z magazynów przyrostowych i tworzyłem je tylko w domyślnym sklepie. Użyłem właściwości Fetched Properties, aby luźno połączyć mój główny wykres obiektów z tymi elementami. Utworzono domyślny magazyn ze wszystkimi instancjami encji w moim modelu. W magazynach przyrostowych albo nie ma elementów "tylko do odczytu" (tj. "Kraj i stan w moim przypadku"), aby rozpocząć lub usunąć je po zakończeniu tworzenia danych.

Kolejnym krokiem jest dodanie magazynu przyrostowego do jego własnego persistentStoreCoordinator (nie to samo, co koordynator domyślnego sklepu, do którego chcemy przenieść całą zawartość) podczas uruchamiania aplikacji.

Ostatnim krokiem jest wywołanie metody migratePersistentStore w magazynie przyrostowym w celu scalenia danych z główną (tj. Domyślną) składnicą. Presto!

Poniższy fragment kodu ilustruje dwa ostatnie kroki wymienione powyżej. Wykonałem te kroki, aby moja konfiguracja scalała dane przyrostowe w głównej składnicy danych do działania.

{ 
    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

Czy możesz wypowiedzieć się na temat skuteczności tego rozwiązania, aby pomóc każdemu, kto rozważa tę opcję, w porównaniu do ręcznego odczytywania i zapisywania danych z jednego sklepu do drugiego? –

1

Powód migracji nie działa, ponieważ zarządzany model obiektu jest identyczny.

Technicznie mówimy o "migracji danych", a nie o "migracji schematu". Interfejs API migracji CoreData jest przeznaczony do migracji schematu, czyli do zarządzania zmianami w modelu zarządzanego obiektu.

Jeśli chodzi o przesyłanie danych z jednego sklepu do drugiego, jesteś w pewnym sensie sam. CoreData może pomóc Ci być wydajnym, używając limitów wsadowych i pobierania na żądanie pobierania, ale musisz wdrożyć logikę samodzielnie.

Wygląda na to, że masz dwa trwałe magazyny, duży i mały. Najskuteczniejsze byłoby załadowanie tego małego i przeanalizowanie go, odkrycie zestawu kluczy podstawowych lub niepowtarzalnych identyfikatorów, o które należy zapytać w większym sklepie.

Następnie można łatwo usunąć duplikaty, po prostu zapytując większy sklep o te identyfikatory.

Dokumentacja NSFetchRequest posiada API do określania zakresu zapytania:

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

+0

Dziękuję za odpowiedź na moje pytanie. Z technicznego punktu widzenia migracja danych w rdzeniu wydaje się robić więcej niż migracja schematu. Moje pytanie próbuje dowiedzieć się, gdzie jest linia i jak mogę wykorzystać to, co już jest do wykonania tej pracy. Chcę uniknąć brutalnej siły - ponieważ prawdopodobnie spowoduje to trudny do utrzymania kod, ponieważ Apple wprowadza coraz więcej funkcji. Poczyniłem pewne postępy, stosując metodę [NSPersistentStore migratePersistentStore ::::]. Jestem prawie na miejscu. Chciałbym, żeby ktoś z doświadczeniem robił to, co próbuję zrobić, może mi doradzić. – Sunny

1

Nie trzeba żadnej migracji - migracja jest przeznaczony do przynieść zmiany w NSManagedObjectModel, a nie w samych danych.

Tym, czego naprawdę potrzebujesz, jest Koordynator sklepu internetowego zarządzający dwoma sklepami trwałymi. To trochę trudne, naprawdę niezbyt trudne.

Istnieje podobne pytanie, które może wyjaśnić, co naprawdę należy zrobić. Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?

Oto dobry arcticle Marcus Zarra

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

+0

Cześć @Nikita, dziękuję za odpowiedź na moje pytanie. Uważam, że program migracjiManager wykonuje więcej niż migrację schematu. W związku z tym dane są migrowane po dodaniu atrybutu do schematu i włączeniu migracji automatycznej. Odnosząc się do twojego komentarza o używaniu jednego persistentStoreCoordinator, właśnie to robię. Zobacz fragment kodu rozpoczynający się od "if (incrementalStoreExists) {." Podane przez Ciebie linki nie rozwiązały mojego problemu. Używam już wielu magazynów trwałych i używam jednego koordynatora do zarządzania nimi. – Sunny

Powiązane problemy