Перенос отношения "многие ко многим" в таблицу объединений в "Основные данные"

У меня есть приложение для iPhone, которое использует отношения "многие ко многим", чтобы связать теги и заметки вместе. В настоящее время я использую функцию "Отношения" с основными данными, чтобы выполнить это, но вместо этого хочу перейти на использование таблицы соединений.

Здесь моя задача: я хотел бы перейти от старой модели к модели join-table, и мне нужно выяснить, как выполнить эту миграцию данных.

Есть ли хорошие примеры того, как это сделать?

Обновление. Я разъясняю свой вопрос здесь, чтобы помочь с тем, что происходит здесь: я хочу попробовать использовать Simperium для поддержки наше приложение, но Simperium не поддерживает отношения "многие ко многим" (!).

В качестве примера того, что я пытаюсь сделать, можно использовать приложение iPhoneCoreDataRecipes в качестве примера.

Здесь моя схема основных данных теперь напоминает: enter image description here

... и вот что я перехожу к: enter image description here

Как мне перейти от одного к другому и принести данные со мной?

Документация Apple для Core Data Migration известна редко, и я не вижу никаких полезных пошаков для использования подкласса NSEntityMapping или NSMigrationManager для выполнения этой работы.

Ответ 1

Вот основной процесс:

  • Создайте копию с версией модели данных. (Выберите модель, затем Редактор- > Добавить версию модели)

  • Внесите свои изменения в новую копию модели данных

  • Отметьте копию новой модели данных в качестве текущей версии. (Щелкните элемент верхнего уровня xcdatamodel, затем в инспекторе файлов установите "Текущая" запись в разделе "Модель версий данных" в новую модель данных, созданную на шаге 1.

  • Обновите объекты модели, чтобы добавить объект RecipeIngredient. Также замените отношения ингредиентов и рецептов на объектах Рецепт и Ингредиент с новыми отношениями, которые вы создали на шаге 2, к объекту RecipeIngredient Entity. (Оба объекта добавили это отношение. Я назвал мои рецептыИнградиенты). Очевидно, везде, где вы создаете отношение от ингредиента к рецепту в старом коде, вам теперь нужно создать объект RecipeIngredient.. но это выходит за рамки этого ответа.

  • Добавить новое сопоставление между моделями (File- > New File...- > (раздел Core Data) → Mapping Model. Это автоматически сгенерирует несколько сопоставлений для вас. RecipeToRecipe, IngredientToIngredient и RecipeIngredient.

  • Удалить отображение рецептуры. Также удалите сопоставления отношений рецептаIngredient, которые он дает вам для RecipeToRecipe и IngredientToRecipe (или как вы их называли на шаге 2).

  • Перетащите карту RecipeToRecipe, чтобы быть последним в списке правил сопоставления. (Это важно, чтобы мы были уверены, что Ингридиенты перенесены до Рецептов, чтобы мы могли связать их, когда мы переносим рецепты.) Миграция пойдет в порядке списка правил миграции,

  • Задайте настраиваемую политику для отображения RecipeToRecipe "DDCDRecipeMigrationPolicy" (это переопределит автоматическую миграцию объектов Recipes и даст нам крючок, где мы можем выполнить логику отображения.

  • Создайте DDCDRecipeMigrationPolicy, выполнив подкласс NSEntityMigrationPolicy for Recipes, чтобы переопределить createDestinationInstancesForSourceInstance (см. код ниже). Это будет вызываться один раз для каждого рецепта, который позволит нам создать объект "Рецепт", а также связанные объекты RecipeIngredient, которые свяжут его с Ingredient. Мы просто позволим Ингридиенту автоматически мигрировать по правилу сопоставления, которое Xcode автоматически создает для нас на шаге 5.

  • Если вы создаете хранилище постоянных объектов (возможно, 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 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-повторе, симуляторе или любом из вышеперечисленных, чтобы он работал.

Ответ 2

Как я уже говорил в комментариях к вопросу, вы можете не захотеть изменить свою модель данных, а скорее создать мост между вашей моделью и библиотекой, которая не понимает отношения "многие ко многим".

Таблица соединений, которую вы хотите создать, на самом деле уже существует, вам просто нужен другой способ представить свои данные в этой библиотеке.

Может ли это работать, зависит от того, как эта библиотека просматривает вашу модель. Существуют различные способы запроса свойств объектов, или может быть, что вы указываете, какие свойства/отношения должны быть скопированы.

Трудно дать реальный ответ, без каких-либо подробностей обо всем этом, но общая идея в том, что:

У вас есть управляемые объекты с заголовками, похожими на:

// 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.

Но, как я уже сказал, это открытый вопрос, если эта библиотека позволяет вам так играть, не разбирая вещи. Если все это кажется вам новым, лучше найти другое решение...