Migreren van een veel-op-veel relatie naar een samenvoegtabel in Core Data

Ik heb een iPhone-app die veel-op-veel-relaties gebruikt om tags en notities aan elkaar te koppelen. Ik gebruik momenteel de "Relaties" -functie van Core Data om dit te bereiken, maar wil in plaats daarvan migreren naar een join-tabel.

Dit is mijn uitdaging: ik wil graag migreren van het oude model naar het join-table-model en ik moet uitzoeken hoe die datamigratie kan worden uitgevoerd.

Zijn er goede voorbeelden van hoe dit te doen?

Update: ik verduidelijk hier mijn vraag om te helpen met wat hier gebeurt: Ik wil proberen om Simperium te gebruiken onze app, maar Simperium biedt geen ondersteuning voor veel-op-veel relaties (!).

Laten we als voorbeeld van wat ik probeer te doen de iPhoneCoreDataRecipes-app als voorbeeld gebruiken.

Here's what my Core Data scheme currently resembles: enter image description here

...and here's what I'm transitioning to: enter image description here

How do I get from one to the other, and bring the data with me?

De documentatie van Apple voor Core Data Migration is notoir dun, en ik zie geen handige walkthroughs voor het gebruik van een NSEntityMapping- of NSMigrationManager-subklasse om de klus te klaren.

10
wat bedoel je met "categorieën"?
toegevoegd de auteur bryanjclark, de bron
Simperium werkt door de entiteiten te nemen en ze te synchroniseren met hun servers. Probleem is, het krijgt alleen attributen, een-op-een en een-op-veel relaties, omdat de veel-op-veel relaties op dit moment niet werken met Simperium.
toegevoegd de auteur bryanjclark, de bron
@mvds, ik heb het gevoel dat je opmerkingen over categorieën hier echt iets op hebben, maar ik kan het niet helemaal visualiseren - zou ik een categorie creëren met de naam RecipeIngredient? Voeg ik de categorie (ReceptIngredient) toe aan de Recept- en Ingrediëntentiteiten? (Sorry als dit basisvragen zijn, dit migratieprobleem is iets nieuws voor mij ...)
toegevoegd de auteur bryanjclark, de bron
Hoe haalt simperium de gegevens uit uw model? Kun je niet gewoon deze lay-out vervalsen door een aantal categorieën toe te voegen aan Recept en Ingrediënt?
toegevoegd de auteur mvds, de bron
met een objectieve c-categorie kunt u methoden en dus nep-eigenschappen toevoegen aan objecten. Simperium kijkt naar uw gegevens door uw entiteiten (en niet door naar de back-up (sqlite) -winkel te kijken?). U kunt dus een methode (/ eigenschap) recipeIngredient toevoegen, die Simperium lijkt te koppelen aan een join-tabel, terwijl het in feite een andere weergave is van uw multi-multi-koppeling (wat in feite is geïmplementeerd als een join-tabel, immers).
toegevoegd de auteur mvds, de bron
Je zult moeten ontdekken hoe Simperium precies zijn werk doet als het naar je entiteiten kijkt. Er zijn verschillende benaderingen die ze hadden kunnen implementeren. Levert u het model, of vindt hun code het automatisch, zo ja: hoe? Etc etc.
toegevoegd de auteur mvds, de bron
ok ik zal wat meer uitleggen in een echt antwoord ...
toegevoegd de auteur mvds, de bron

2 antwoord

Dit is het basisproces:

  1. Create a versioned copy of the Data Model. (Select the Model, then Editor->Add Model Version)

  2. Make your changes to the new copy of the data model

  3. Mark the copy of the new data model as the current version. (Click the top level xcdatamodel item, then in the file inspector set the "Current" entry under "Versioned Data Model" section to the new data model you created in step 1.

  4. Update your model objects to add the RecipeIngredient entity. Also replace the ingredients and recipes relationships on Recipe and Ingredient entities with new relationships you created in step 2 to the RecipeIngredient Entity. (Both entities get this relation added. I called mine recipeIngredients) Obviously wherever you create the relation from ingredient to recipe in the old code, you'll now need to create a RecipeIngredient object.. but that's beyond the scope of this answer.

  5. Add a new Mapping between the models (File->New File...->(Core Data section)->Mapping Model. This will auto-generate several mappings for you. RecipeToRecipe, IngredientToIngredient and RecipeIngredient.

  6. Delete the RecipeIngredient Mapping. Also delete the recipeIngredient relation mappings it gives you for RecipeToRecipe and IngredientToRecipe (or whatever you called them in step 2).

  7. Drag the RecipeToRecipe Mapping to be last in the list of Mapping Rules. (This is important so that we're sure the Ingredients are migrated before the Recipes so that we can link them up when we're migrating recipes.) The migration will go in order of the migration rule list.

  8. Set a Custom Policy for the RecipeToRecipe mapping "DDCDRecipeMigrationPolicy" (This will override the automatic migration of the Recipes objects and give us a hook where we can perform the mapping logic.

  9. Create DDCDRecipeMigrationPolicy by subclassing NSEntityMigrationPolicy for Recipes to override createDestinationInstancesForSourceInstance (See Code Below). This will be called once for Each Recipe, which will let us create the Recipe object, and also the related RecipeIngredient objects which will link it to Ingredient. We'll just let Ingredient be auto migrated by the mapping rule that Xcode auto create for us in step 5.

  10. Wherever you create your persistent object store (probably AppDelegate), ensure you set the user dictionary to auto-migrate the data model:

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

Subklasse NSEntityMigrationPolicy for Recipes

#import 
@interface DDCDRecipeMigrationPolicy : NSEntityMigrationPolicy
@end

* Negeer createDestinationInstancesForSourceInstance in 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;
}

Ik zou een foto van de setup in Xcode en het voorbeeld Xcode-project plaatsen, maar ik schijn nog geen reputatie te hebben op stack overflow ... dus dat laat ik niet toe. Ik zal dit ook op mijn blog posten. bingosabi.wordpress.com/.

Merk ook op dat het Xcode Core Data-modellering-spul een beetje schilferig is en af ​​en toe een "schone", goede Xcode-rester, simulator-stuitering of al het bovenstaande nodig heeft om het te laten werken.

24
toegevoegd
wauw dat is een geweldig antwoord. Bedankt, Ben.
toegevoegd de auteur bryanjclark, de bron
Goed antwoord! Volgens de documenten moet u, omdat u super niet handmatig belt: [manager associateSourceInstance: sInstance withDestinationInstance: newRecipe forEntityMapping: mapping]; aan het einde van de methode .
toegevoegd de auteur pgb, de bron
Ik denk dat ik een fout heb die wordt veroorzaakt door onjuiste volgorde van kaarten, maar ik kan ze niet slepen zoals beschreven in Xcode 5 - is er een trucje aan?
toegevoegd de auteur Ash, de bron
@PushpRaj U kunt zeker relaties creëren in createDestinationInstancesForSourceInstance en deze succesvol laten migreren, ook al zijn de documenten hier developer.apple.com/library/ios/documentation/Cocoa/Conceptu‌ al/& hellip; stellen voor dat u createRelationshipsForDestinationInstance createRelationshipsForDestinationInstance implementeert in plaats daarvan. verwarrend om het zachtjes uit te drukken
toegevoegd de auteur wyu, de bron
Ik twijfel hier, het lijkt erop dat u ook relaties in deze code aan het instellen bent tijdens het gebruik van: [newRecipeIngredient setValue: newRecipe forKey: @ "recipe"]; is dit niet de bedoeling dat deze methode alleen instanties maakt en geen relaties instelt?
toegevoegd de auteur PushpRaj, de bron

Zoals ik in de opmerkingen over de vraag suggereerde, wilt u misschien niet datamodel wijzigen, maar eerder een brug slaan tussen uw model en de bibliotheek die geen veel-op-veel relaties begrijpt.

De jointabel die u wilt maken, is eigenlijk al aanwezig, u hoeft alleen maar een andere manier te vinden om uw gegevens aan deze bibliotheek te presenteren.

Of dit zou kunnen werken, hangt af van hoe deze bibliotheek naar uw model kijkt. Er zijn verschillende manieren om de eigenschappen van uw entiteiten te doorzoeken, of het kan zijn dat u degene bent die opgeeft welke eigenschappen/relaties moeten worden gekopieerd.

Het is moeilijk om een ​​echt antwoord te geven, zonder enige details over dit alles, maar het algemene idee is dat:

U hebt enkele beheerde objecten met kopteksten die er als volgt uitzien:

// Recipe.h

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

en nu voeg je een aantal extra methoden toe aan dit object, gebruikmakend van een categorie:

// Recipe+fakejoin.h

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

en een implementatie in Recept + fakejoin.m van deze methode die een NSSet met RecipeIngredients -objecten retourneert.

Maar zoals ik al zei, het is een open vraag of je met deze bibliotheek op deze manier kunt spelen zonder dingen te verbreken. Als dit allemaal nieuw voor je klinkt, kun je beter een andere oplossing vinden ...

1
toegevoegd
Bedankt mvds, maar ik heb gesproken met de Simperium-mensen en het klinkt niet dat deze oplossing het probleem zou oplossen. Ik waardeer echter de moeite in uw antwoord! Simperium vereist dat er echte kerngegevensrepresentaties zijn voor de gegevens.
toegevoegd de auteur bryanjclark, de bron
Maar hoe ziet een "werkelijke weergave van de kerngegevens" eruit? Je kunt bijna alles nep in obj-c.
toegevoegd de auteur mvds, de bron
@mvds Een "werkelijke weergave van de kerngegevens" is hier waarschijnlijk de entiteitslay-out zoals gedefinieerd in het CoreData beheerde objectmodel . En u kunt dat niet "faken" door enkele eigenschappen aan de gegenereerde NSManagedObject-subklassen toe te voegen.
toegevoegd de auteur Daniel Rinser, de bron