Перенос отношения «многие ко многим» в таблицу соединений в Core Data

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

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

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

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

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

Вот как сейчас выглядит моя схема основных данных: введите здесь описание изображения

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

Как мне перейти от одного к другому и взять с собой данные?

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


person bryanjclark    schedule 24.06.2012    source источник
comment
Как simperium получает данные из вашей модели? Разве вы не можете просто подделать этот макет, добавив несколько категорий в рецепты и ингредиенты?   -  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. Мы просто разрешим автоматическую миграцию 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