2012-04-19 8 views
11

Mam projekt iOS z dużą, wstępnie załadowaną bazą danych i małą bazą danych użytkowników (oba bazy danych SQLite CoreData). Poprzednie pytania sugerują stosowanie konfiguracji do kontrolowania, które jednostki są używane w danym sklepie. Mam problem z tym, żeby to zadziałało. Oto, co starałem ...CoreData z wieloma sklepami: problemy konfiguracyjne

- (NSManagedObjectModel *)managedObjectModel 
{ 
    if (_managedObjectModel != nil) return _managedObjectModel; 
    // set up the model for the preloaded data 
    NSURL *itemURL = [[NSBundle mainBundle] URLForResource:@"FlagDB" withExtension:@"momd"]; 
    NSManagedObjectModel *itemModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:itemURL]; 
    // set up the model for the user data 
    NSURL *userDataURL = [[NSBundle mainBundle] URLForResource:@"UserData" withExtension:@"momd"]; 
    NSManagedObjectModel *userDataModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:userDataURL]; 
    // merge the models 
    _managedObjectModel = [NSManagedObjectModel modelByMergingModels:[NSArray arrayWithObjects:itemModel, userDataModel, nil]]; 
    // define configurations based on what was in each model 
WRONG [_managedObjectModel setEntities:itemModel.entities forConfiguration:@"ItemData"]; 
WRONG [_managedObjectModel setEntities:userDataModel.entities forConfiguration:@"UserData"]; 
    return _managedObjectModel; 
} 

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
{ 
    if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator; 
    // preloaded data is inside the bundle 
    NSURL *itemURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"FlagDB.sqlite"]; 
    // user data is in the application directory 
    NSURL *userDataURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"UserData.sqlite"]; 

    NSManagedObjectModel *mom = self.managedObjectModel; 
    NSError *error = nil; 
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; 

    if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:nil error:&error]) 
    { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 
    ... 

ten przerywa z „Model używany do otwarcia sklepu jest niezgodna z użytej do stworzenia sklepu”. Sprawdzanie skrótów w modelu w odniesieniu do skrótów w sklepie wskazuje, że są one identyczne dla elementów znajdujących się w konfiguracji ItemData.

Gdy próbuję robić lekkie migracji, tak jak poniżej:

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

    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; 
    if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:options error:&error]) 

To nie z '', NSInvalidArgumentException powodu: ' '' model nie zawiera konfigurację' itemData Zakładam, że dzieje się tak dlatego, że w procesie migracji jest tworzony nowy model i nie zawiera on mojej konfiguracji.

Na podstawie kilku sugestii z innych wątków próbowałem wykonać niewielką migrację bez konfiguracji, a następnie utworzyć nowego koordynatora za pomocą konfiguracji. Ten rodzaj prac, ale dodaje tabele do wstępnie załadowanego pliku .sqlite odpowiadającego encji danych użytkownika (które nie należą do niego) i tworzy zarówno wstępnie załadowane tabele danych, jak i tabele danych użytkowników w nowo utworzonym magazynie danych użytkownika . Wynik końcowy jest taki, że pobieranie kończy się niepowodzeniem, pozornie dlatego, że szukają w niewłaściwym sklepie.

NSDictionary *migrationOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 

// make a temp persistent store coordinator to handle the migration 
NSPersistentStoreCoordinator *tempPsc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; 
// migrate the stores 
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:itemURL options:migrationOptions error:&error]) 
{ 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
} 
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:userDataURL options:migrationOptions error:&error]) 
{ 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
} 

// make a permanent store coordinator 
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; 

NSDictionary *readOnlyOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSReadOnlyPersistentStoreOption, nil]; 
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData" URL:itemURL options:readOnlyOptions error:&error]) 
{ 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
} 

/*if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"UserData" URL:userDataURL options:nil error:&error]) 
{ 
NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
abort(); 
}*/ 

A potem ...

OSAppDelegate *delegate = [UIApplication sharedApplication].delegate; 
    NSManagedObjectContext *context = delegate.managedObjectContext; 
    // sanity check 
    for (NSPersistentStore *store in context.persistentStoreCoordinator.persistentStores) { 
     NSLog(@"store %@ -> %@", store.configurationName, store.URL); 
     NSMutableArray *entityNames = [[NSMutableArray alloc] init]; 
     for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entitiesForConfiguration:store.configurationName]) { 
      [entityNames addObject:entity.name]; 
     } 
     NSLog(@"entities: %@", entityNames); 
    } 

    NSFetchRequest *categoryFetchRequest = [[NSFetchRequest alloc] init]; 
    categoryFetchRequest.entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:context]; 
    categoryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", categoryName]; 
    NSError *error = nil; 
    Category *category = [[delegate.managedObjectContext executeFetchRequest:categoryFetchRequest error:&error] lastObject]; 

Działa to dobrze, wracając odpowiednio nazwany kategorię obiektu, aż odkomentowaniu dodanie drugiego sklepu. Jeśli to zrobię, wynik pobierania wróci pusta. Diagnostyczne komunikaty NSLog drukują dokładnie to, czego się spodziewam. Każdy sklep jest powiązany z poprawną konfiguracją, a każda konfiguracja ma odpowiednie elementy.

Czy ktoś może wskazać mi kod źródłowy do konfiguracji wielu sklepów lub wskazać, co robię źle? Z góry dziękuję!


SOLVED: Sedno problemu były dwie linie oznaczone źle w pierwszej listingu. Próbowałem programowo tworzyć konfiguracje, ale wydaje się to niewystarczające. Jeśli wyszukujesz ManagedObjectModel dla konfiguracji po wykonaniu tej czynności, rzeczywiście widzisz konfiguracje na liście i poprawne encje są powiązane z tymi konfiguracjami. Wydaje się jednak, że należy zrobić coś jeszcze, aby narzędzie PersistentStoreCoordinator mogło właściwie z nich korzystać. Utworzenie konfiguracji w Xcode sprawia, że ​​działają.


FOLLOW UP: Jest dodatkowy szkopuł. Rozwiązanie polegające na uruchomieniu oddzielnego przejścia migracji przed skonfigurowaniem ostatecznego Koordynatora magazynu trwałego działa świetnie ... w symulatorze. Na rzeczywistym urządzeniu uprawnienia są bardziej rygorystyczne. Jeśli spróbujesz przeprowadzić tę migrację, nie powiedzie się, ponieważ magazyn w pakiecie aplikacji jest tylko do odczytu. Migracja wydaje się konieczna, chyba że skonsolidujesz swoje modele. Jeśli masz tylko jeden model, a sklep w pakiecie aplikacji jest z nim kompatybilny, migracja nie jest konieczna i możliwy jest dostęp przy użyciu konfiguracji zdefiniowanych w Xcode.

Inną opcją może być przeniesienie danych do katalogu Dokumenty przed przystąpieniem do migracji. Nie sprawdziłem, czy to podejście działa.

+0

Upewnij się, że wykonujesz migrację w katalogu dokumentów użytkownika piaskownicy - który jest przeznaczony do odczytu/zapisu - a nie w samym pakiecie aplikacji. – Sunny

+0

Nie chciałem przenosić danych do katalogu dokumentów, ponieważ nie chcę, aby te (statyczne) dane były archiwizowane i liczone względem przydziału iCloud użytkownika. Ale wygląda na to, że w iOS 5.0.1 istnieje sposób oznaczania plików, których nie można kopiować: http://developer.apple.com/library/ios/#qa/qa1719/_index.html – Aneel

+2

Cóż, zainspirowałeś mnie i po spędzeniu kilku godzin na rozwiązaniu mojego problemu napisałem pełny artykuł na ten temat [tutaj] (http://blog.atwam.com/blog/2012/05/11/multiple-persistent-stores-and-seed-data -with-core-data /). Pomyślałem, że może pomóc innym ludziom w przyszłości. – Wam

Odpowiedz

5

Czy próbowałeś, aby obie konfiguracje były zdefiniowane w tym samym modelu (tj. Tym samym momd)? Możesz to łatwo zrobić, wybierając "Edytor-> Dodaj konfigurację" podczas edytowania jednego z modeli danych. Przeciągnij elementy dla UserData i ItemData do odpowiedniej konfiguracji. Konfiguracja określona w ten sposób jest zgodna z Core Data; nie chodzi o nazwę pliku/adresu URL. Gdy już to zrobisz, uprość powyższy _managedObjectModel powyżej, aby wyszukać pojedynczy plik/adres URL momd po jego wywołaniu.

Ewentualnie, jeśli zdecydujesz się zachować dwa oddzielne pliki momd, upewnij się, że odpowiednio zdefiniowałeś swoje modele w Konfiguracjach o nazwach "UserData" i "ItemData" w swoich plikach definicji modelu.

Moja pierwsza sugestia to zachowanie jednego pliku modelu. O ile nie ma powodu, dla którego te konfiguracje nie mogą znajdować się w tym samym modelu obiektowym, nie ma sensu komplikować rzeczy za pomocą wielu plików. Sądzę, że trudno byłoby sfinalizować dane podstawowe do robienia tego, do czego jesteście zobowiązani. Spróbuj uprościć część modelowania kodu.

+1

Dzięki za odpowiedź. Mam dobry powód, aby korzystać z dwóch oddzielnych modeli. Model danych pozycji jest współdzielony z innym projektem (aplikacja OS X używana do tworzenia/edycji zbioru danych). Jeśli to możliwe, chciałbym móc zachować dwa modele oddzielnie. – Aneel

+1

Próbowałem tego, co sugerujesz, i to działa. Skopiowałem model danych użytkownika do modelu danych pozycji i utworzyłem dwie konfiguracje w XCode. Muszę utworzyć tymczasowy PSC i przeprowadzić niewielką migrację bez konfiguracji w każdym z magazynów danych, a następnie utworzyć kolejny PSC i dodać każdy sklep z odpowiednią konfiguracją. Bez tych kroków nadal otrzymuję błędy. Dzięki nim PSC kojarzy każdy podmiot z właściwym magazynem. Myślę, że ujednolicony model jest mniej nieelegancki niż moje inne rozwiązanie polegające na posiadaniu dwóch oddzielnych MOM/PSC/MOC. Dzięki! Nadal chciałbym to zrobić w dwóch osobnych modelach. – Aneel

+1

Dobra, próbowałem także tego, co sugerujesz, aby modele były rozdzielone. To też działa! Wydaje się, że sedno mojego problemu polegało na tym, że definiowanie konfiguracji w sposób programistyczny za pomocą AddDance: ManagedObjectModel: forConfiguration: nie działa. Pojawiają się, gdy wyszukujesz MOM dla swoich konfiguracji, ale w rzeczywistości nie wydają się być właściwie używane przez PSC. Tworzenie konfiguracji w Xcode musi robić więcej za kulisami. – Aneel