2012-03-06 110 views
0

我已啓用Core Data模型的版本控制,並且使用輕量級遷移。我的代碼總是試圖執行輕量級遷移,然後如果因爲模型不兼容而失敗,它將回退到刪除所有現有數據並從服務器重新獲取。 所以輕量級遷移僅用於提高效率,而不是正確性所必需的。CoreData版本控制和阻止輕量級遷移

我現在想要做的是對我的模型進行更改,理論上輕量級遷移可以處理,但實際上我需要來自服務器的新數據。我想以某種方式標記模型並且不能通過輕量級遷移進行升級。例如,如果字段名稱未更改,但該字段的含義已更改,舊代碼與新代碼庫不兼容。 (這只是一個例子。)

有沒有人找到一種方法來標記兩個模型不兼容,所以輕量級的遷移不會升級它們?

回答

3

我以前一直在努力解決同樣的問題。

我有一個方法,將試圖使用映射模型遷移數據,這是你應該使用,如果你要關閉輕量級遷移。

如果你不打算做很多花哨的數據映射,xcode會自動創建一個映射模型,它可以像輕量級遷移一樣工作。您只需在每次向Core Data添加新版本時創建一個新的「Mapping Model」文件。只需進入「文件 - >新建 - >新建文件」,在覈心數據下,應該有一個映射模型模板。選擇它並選擇源和目標版本。

我沒有我的代碼公開可用在github上,所以我只是在這裏發佈遷移方法。

- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel 
{ 
    NSError *error = nil; 

    // if store dosen't exist skip migration 
    NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; 
    if(![NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir]) 
    { 
     migrationProgress = 1.0; 
     [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES]; 

     // remove migration view 
     [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES]; 
     [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES]; 

     self.migrationView = nil; 
     self.migrationProgressLabel = nil; 
     self.migrationProgressView = nil; 
     self.migrationSpinner = nil; 

     return YES; 
    } 

    //START:progressivelyMigrateURLHappyCheck 
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:&error]; 

    if (!sourceMetadata) 
    { 
     return NO; 
    } 

    if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata]) 
    { 
     migrationProgress = 1.0; 
     [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES]; 

     // remove migration view 
     [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES]; 
     [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES]; 

     self.migrationView = nil; 
     self.migrationProgressLabel = nil; 
     self.migrationProgressView = nil; 
     self.migrationSpinner = nil; 

     error = nil; 
     return YES; 
    } 
    else 
    { 
     migrationProgress = 0.0; 
     [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:NO waitUntilDone:YES]; 
     [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];   
    } 
    //END:progressivelyMigrateURLHappyCheck 

    //START:progressivelyMigrateURLFindModels 
    //Find the source model 
    NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata]; 
    if(sourceModel == nil) 
    { 
     NSLog(@"%@", [NSString stringWithFormat:@"Failed to find source model\n%@", [sourceMetadata description]]); 
     return NO; 
    } 

    //Find all of the mom and momd files in the Resources directory 
    NSMutableArray *modelPaths = [NSMutableArray array]; 
    NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" inDirectory:nil]; 
    for (NSString *momdPath in momdArray) 
    { 
     NSAutoreleasePool *pool = [NSAutoreleasePool new]; 
     NSString *resourceSubpath = [momdPath lastPathComponent]; 
     NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:resourceSubpath]; 
     [modelPaths addObjectsFromArray:array]; 
     [pool drain]; 
    } 

    NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:nil]; 
    [modelPaths addObjectsFromArray:otherModels]; 

    if (!modelPaths || ![modelPaths count]) 
    { 
     //Throw an error if there are no models 
     NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
     [dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey]; 

     //Populate the error 
     error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict]; 
     if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"]) 
     { 
      NSLog(@"error: %@", error); 
     } 
     return NO; 
    } 
    //END:progressivelyMigrateURLFindModels 

    //See if we can find a matching destination model 
    //START:progressivelyMigrateURLFindMap 
    NSMappingModel *mappingModel = nil; 
    NSManagedObjectModel *targetModel = nil; 
    NSString *modelPath = nil; 

    for(modelPath in modelPaths) 
    { 
     targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]]; 
     mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel]; 

     //If we found a mapping model then proceed 
     if(mappingModel) 
     { 
      break; 
     } 
     else 
     { 
      //Release the target model and keep looking 
      [targetModel release]; 
      targetModel = nil; 
     } 
    } 

    //We have tested every model, if nil here we failed 
    if (!mappingModel) 
    { 
     NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
     [dict setValue:@"No mapping models found in bundle" forKey:NSLocalizedDescriptionKey]; 
     error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict]; 
     if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"]) 
     { 
      NSLog(@"error: %@", error); 
     } 
     return NO; 
    } 
    //END:progressivelyMigrateURLFindMap 

    //We have a mapping model and a destination model. Time to migrate 
    //START:progressivelyMigrateURLMigrate 
    NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel]; 

    // reg KVO for migration progress 
    [manager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL]; 

    NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension]; 
    NSString *storeExtension = [[sourceStoreURL path] pathExtension]; 
    NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension]; 

    //Build a path to write the new store 
    storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension]; 
    NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath]; 

    if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error]) 
    { 
     if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"]) 
     { 
      NSLog(@"error: %@", error); 
     } 
     [targetModel release]; 
     [manager removeObserver:self forKeyPath:@"migrationProgress"]; 
     [manager release]; 
     return NO; 
    } 
    [targetModel release]; 
    [manager removeObserver:self forKeyPath:@"migrationProgress"]; 
    [manager release]; 
    //END:progressivelyMigrateURLMigrate 

    //Migration was successful, move the files around to preserve the source 
    //START:progressivelyMigrateURLMoveAndRecurse 
    NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; 
    guid = [guid stringByAppendingPathExtension:modelName]; 
    guid = [guid stringByAppendingPathExtension:storeExtension]; 
    NSString *appSupportPath = [storePath stringByDeletingLastPathComponent]; 
    NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid]; 

    NSFileManager *fileManager = [NSFileManager defaultManager]; 
    if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:&error]) 
    { 
     if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"]) 
     { 
      NSLog(@"error: %@", error); 
     } 
     //Failed to copy the file 
     return NO; 
    } 

    //Move the destination to the source path 
    if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:&error]) 
    { 
     if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"]) 
     { 
      NSLog(@"error: %@", error); 
     } 
     //Try to back out the source move first, no point in checking it for errors 
     [fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil]; 
     return NO; 
    } 

    //We may not be at the "current" model yet, so recurse 
    return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel]; 
    //END:progressivelyMigrateURLMoveAndRecurse 
} 

這是我從某些核心數據手冊中獲得的一種編輯版本,我不記得標題了。我希望我能給作者一點功勞。 :S

當心,我在這裏的一些代碼,你應該在你執行刪除。這主要是我用來更新遷移進度視圖的東西。

您可以使用此方法,像這樣:

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"YongoPal.sqlite"]; 

// perform core data migrations if necessary 
if(![self progressivelyMigrateURL:storeURL ofType:NSSQLiteStoreType toModel:self.managedObjectModel]) 
{ 
    // reset the persistent store on fail 
    NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; 
    NSError *error = nil; 
    [[NSFileManager defaultManager] removeItemAtPath:[NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir] error:&error]; 
} 
else 
{ 
    NSLog(@"migration succeeded!"); 
} 

記住您使用此之前刪除輕量級遷移選項。

+0

我不遵循這段代碼如何解決我的問題。重申一下,我需要一種說法,兩種模式不兼容。我應該在XCode中創建映射模型文件後做些什麼? – vm2000 2012-03-07 20:40:55

+0

您創建映射模型試圖從內部的Xcode(4.x版)查看它後,它應該拉起實體映射列表和屬性映射。從屬性映射列表中,您應該看到目標和值表達式。只需從遷移中刪除您希望從EXCLUDE中移除的屬性的值表達式(應該看起來像$ source。{attribute_name}),以便它是空的。當你運行漸進式遷移URL:ofType:toModel:方法時,它將遷移除空值表達式的屬性以外的所有內容。 – 2012-03-08 09:42:45

+2

該方法來自Marcus S. Zarra的「核心數據:用於在Mac OSX上保存數據的Apples API」。我認爲它現在被稱爲「核心數據:iOS,OS X和iCloud的數據存儲和管理」.. – david 2013-12-09 09:29:24

0

Apple的Core Data Model Versioning and Data Migration Programming Guide解決了如果「你有兩個版本的模型,Core Data通常會認爲你想被識別爲不同的模型」,我認爲這就是你所要求的。

答案是設置versionHashModifier你的實體或屬性的新模式之一。

在你的例子中,你會這樣做的意義已經改變的領域。