Мигриране на връзка много към много към таблица за свързване в Core Data

Имам приложение за iPhone, което използва релации много към много, за да свързва тагове и бележки заедно. В момента използвам функцията „Връзки“ на Core Data, за да постигна това, но бих искал вместо това да мигрирам към използване на таблица за присъединяване.

Ето моето предизвикателство: Бих искал да мигрирам от стария модел към модела на таблицата за присъединяване и трябва да разбера как да извърша тази миграция на данни.

Има ли добри примери как да направите това?

Актуализация: Изяснявам въпроса си тук, за да помогна със случващото се тук: Искам да опитам да използвам Simperium за поддръжка нашето приложение, но Simperium не поддържа връзки много към много (!).

Като пример за това, което се опитвам да направя, нека използваме приложението iPhoneCoreDataRecipes като пример.

Ето на какво в момента прилича моята схема за основни данни: въведете описание на изображението тук

...и ето към какво преминавам: въведете описание на изображението тук

Как да стигна от единия до другия и да нося данните със себе си?

Документацията на Apple за Core Data Migration е изключително оскъдна и не виждам полезни указания за използване на подклас NSEntityMapping или NSMigrationManager, за да свършим работата.


person bryanjclark    schedule 24.06.2012    source източник
comment
Как simperium получава данните от вашия модел? Не можете ли просто да фалшифицирате това оформление, като добавите някои категории към Recipe и Ingredient?   -  person mvds    schedule 29.06.2012
comment
какви категории имаш предвид?   -  person bryanjclark    schedule 29.06.2012
comment
Simperium работи, като взема обектите и ги синхронизира с техните сървъри. Проблемът е, че той получава само атрибути, връзки "един към един" и "един към много", тъй като връзките "много към много" не работят със Simperium в момента.   -  person bryanjclark    schedule 29.06.2012
comment
с обективна c категория можете да добавяте методи и следователно фалшиви свойства към обекти. Simperium преглежда вашите данни чрез вашите обекти (а не като гледа резервното (sqlite) хранилище?). Така че можете да добавите метод(/свойство) recipeIngredient, който ще изглежда на Simperium като връзка към таблица за присъединяване, докато всъщност това е просто различен изглед на вашата мулти-мулти връзка (която всъщност е имплементирана като таблица за присъединяване, след всичко).   -  person mvds    schedule 29.06.2012
comment
Ще трябва да разберете как точно Simperium върши работата си, когато разглежда вашите обекти. Има различни подходи, които биха могли да приложат. Вие доставяте ли модела или кодът им го намира автоматично, ако е така: как? и т.н.   -  person mvds    schedule 29.06.2012
comment
@mvds, имам чувството, че вашите коментари за категории наистина са насочени към нещо тук, но не мога да го визуализирам съвсем -- бих ли създал категория, наречена RecipeIngredient? Добавям ли категорията (RecipeIngredient) към обектите Recipe и Ingredient? (Съжалявам, ако това са основни въпроси; този проблем с миграцията е нещо ново за мен...)   -  person bryanjclark    schedule 29.06.2012
comment
добре, ще опиша малко повече в истински отговор...   -  person mvds    schedule 29.06.2012


Отговори (2)


Ето основния процес:

  1. Създайте версионно копие на модела на данни. (Изберете модела, след това Редактор->Добавяне на версия на модела)

  2. Направете вашите промени в новото копие на модела на данни

  3. Маркирайте копието на новия модел на данни като текущата версия. (Щракнете върху елемента от най-високо ниво xcdatamodel, след което във файловия инспектор задайте записа „Текущ“ в раздела „Модел на данни с версии“ ​​към новия модел на данни, който сте създали в стъпка 1.

  4. Актуализирайте своите моделни обекти, за да добавите обекта RecipeIngredient. Също така заменете връзките на съставките и рецептите в обектите Recipe и Ingredient с нови взаимоотношения, които сте създали в стъпка 2 към обекта RecipeIngredient. (И при двата обекта се добавя тази релация. Нарекох моята recipeIngredients) Очевидно където и да създадете релацията от съставка към рецепта в стария код, сега ще трябва да създадете обект RecipeIngredient... но това е извън обхвата на този отговор.

  5. Добавете ново съпоставяне между моделите (Файл->Нов файл...->(секция Основни данни)->Модел на съпоставяне. Това автоматично ще генерира няколко съпоставяния за вас. RecipeToRecipe, IngredientToIngredient и RecipeIngredient.

  6. Изтрийте съпоставянето на RecipeIngredient. Също така изтрийте съпоставянията на релациите recipeIngredient, които ви дава за RecipeToRecipe и IngredientToRecipe (или както сте ги нарекли в стъпка 2).

  7. Плъзнете съпоставянето RecipeToRecipe, за да бъде последно в списъка с правила за съпоставяне. (Това е важно, за да сме сигурни, че съставките са мигрирани преди рецептите, за да можем да ги свържем, когато мигрираме рецепти.) Миграцията ще върви по реда на списъка с правила за мигриране .

  8. Задайте персонализирана политика за съпоставянето на RecipeToRecipe „DDCDRecipeMigrationPolicy“ (Това ще замени автоматичната миграция на обектите Recipes и ще ни даде кука, където можем да извършим логиката на съпоставяне.

  9. Създайте DDCDRecipeMigrationPolicy чрез подклас NSEntityMigrationPolicy за рецепти, за да замените createDestinationInstancesForSourceInstance (вижте кода по-долу). Това ще бъде извикано веднъж за всяка рецепта, което ще ни позволи да създадем обекта Recipe, както и свързаните обекти RecipeIngredient, които ще го свържат с Ingredient. Просто ще оставим Съставката да бъде автоматично мигрирана от правилото за картографиране, което Xcode автоматично създава за нас в стъпка 5.

  10. Където и да създадете хранилището си за постоянни обекти (вероятно AppDelegate), уверете се, че сте задали потребителския речник за автоматично мигриране на модела на данни:

if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
      configuration:nil 
      URL:storeURL 
      options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,  nil] 
      error:&error])
{
}

Подклас NSEntityMigrationPolicy за рецепти

#import <CoreData/CoreData.h>
@interface DDCDRecipeMigrationPolicy : NSEntityMigrationPolicy
@end

*Замяна на createDestinationInstancesForSourceInstance в DDCDRecipeMigrationPolicy.m *

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error
{

    NSLog(@"createDestinationInstancesForSourceInstance : %@", sInstance.entity.name);

   //We have to create the recipe since we overrode this method. 
   //It's called once for each Recipe.  
    NSManagedObject *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:@"Recipe" inManagedObjectContext:[manager destinationContext]];
    [newRecipe setValue:[sInstance valueForKey:@"name"] forKey:@"name"];
    [newRecipe setValue:[sInstance valueForKey:@"overview"] forKey:@"overview"];
    [newRecipe setValue:[sInstance valueForKey:@"instructions"] forKey:@"instructions"];

    for (NSManagedObject *oldIngredient in (NSSet *) [sInstance valueForKey:@"ingredients"])
    {
        NSFetchRequest *fetchByIngredientName = [NSFetchRequest fetchRequestWithEntityName:@"Ingredient"];
        fetchByIngredientName.predicate = [NSPredicate predicateWithFormat:@"name = %@",[oldIngredient valueForKey:@"name"]];

        //Find the Ingredient in the new Datamodel.  NOTE!!!  This only works if this is the second entity migrated.
         NSArray *newIngredientArray = [[manager destinationContext] executeFetchRequest:fetchByIngredientName error:error];

        if (newIngredientArray.count == 1)
        {
             //Create an intersection record. 
            NSManagedObject *newIngredient = [newIngredientArray objectAtIndex:0];
            NSManagedObject *newRecipeIngredient = [NSEntityDescription insertNewObjectForEntityForName:@"RecipeIngredient" inManagedObjectContext:[manager destinationContext]];
            [newRecipeIngredient setValue:newIngredient forKey:@"ingredient"];
            [newRecipeIngredient setValue:newRecipe forKey:@"recipe"];
             NSLog(@"Adding migrated Ingredient : %@ to New Recipe %@", [newIngredient valueForKey:@"name"], [newRecipe valueForKey:@"name"]);
        }


    }

    return YES;
}

Бих публикувал снимка на настройката в Xcode и примерния проект на Xcode, но изглежда все още нямам точки за репутация при препълване на стека... така че не ми позволява. Ще публикувам това и в моя блог. bingosabi.wordpress.com/.

Също така имайте предвид, че картографирането на модела на Xcode Core Data е малко нестабилно и от време на време се нуждае от „чист“, добър рестер на Xcode, отскачане на симулатора или всичко по-горе, за да работи.

person Ben Bruckhart    schedule 02.07.2012
comment
Имам малко съмнения тук, изглежда, че задавате отношения и в този код, докато използвате: [newRecipeIngredient setValue:newRecipe forKey:@recipe]; този метод не трябва ли да създава само екземпляри, а не да задава отношения? - person PushpRaj; 28.08.2012
comment
Страхотен отговор! Според документите, тъй като не извиквате super, трябва ръчно да извикате: [manager associateSourceInstance:sInstance withDestinationInstance:newRecipe forEntityMapping:mapping]; близо до края на метода. - person pgb; 22.05.2013
comment
Мисля, че имам грешка, причинена от неправилен ред на картографиране, но изглежда не мога да ги плъзгам, както е описано в Xcode 5 - има ли трик? - person Ash; 06.03.2014
comment
@PushpRaj Определено можете да създавате връзки в createDestinationInstancesForSourceInstance и да мигрирате успешно, въпреки че документите тук developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ предлагаме да внедрите createRelationshipsForDestinationInstance вместо това. меко казано объркващо - person wyu; 06.09.2016

Както предложих в коментарите по въпроса, може да не искате да промените вашия модел на данни, а по-скоро да създадете мост между вашия модел и библиотеката, която не разбира отношенията много към много.

Обединената таблица, която искате да създадете, всъщност вече е там, просто ви трябва друг начин да представите вашите данни в тази библиотека.

Дали това може да работи зависи от това как тази библиотека гледа на вашия модел. Има различни начини да прави заявки за свойствата на вашите обекти или може да се окаже, че вие ​​сте този, който указва кои свойства/отношения да бъдат копирани.

Трудно е да се даде истински отговор, без никакви подробности за всичко това, но общата идея е, че:

Имате някои управлявани обекти със заглавки, изглеждащи така:

// Recipe.h

@interface Recipe : NSManagedObject
@property (nonatomic,retain) NSSet *ingredients;
@end

и сега добавяте към този обект някои допълнителни методи, като използвате категория:

// Recipe+fakejoin.h

@interface Recipe (fakejoin)
-(NSSet*)recipeIngredients;
@end

и реализация в Recipe+fakejoin.m на този метод, която връща NSSet с RecipeIngredients обекта.

Но както казах, открит е въпросът дали тази библиотека ви позволява да играете по този начин, без да счупите нещата. Ако всичко това ви звучи ново, по-добре намерете друго решение...

person mvds    schedule 29.06.2012
comment
Благодаря, mvds, но разговарях с хората от Simperium и не изглежда, че това решение ще реши проблема. Все пак оценявам усилията в отговора ви! Simperium изисква да има действителни представяния на основните данни за данните. - person bryanjclark; 01.07.2012
comment
Но как изглежда действителното представяне на основните данни? Можете да фалшифицирате почти всичко в obj-c. - person mvds; 01.07.2012
comment
@mvds Едно действително представяне на основни данни тук вероятно е оформлението на обекта, както е дефинирано в CoreData модела на управлявани обекти. И не можете да фалшифицирате това, като добавите някои свойства към генерираните NSManagedObject подкласове. - person Daniel Rinser; 29.04.2014