2013-03-12 5 views
10

Ich habe Mühe, eine vernünftige Lösung für ein Problem zu finden, das bei der Verwendung verschachtelter Kontexte für verwaltete Objekte in Core Data auftritt. Nehmen Sie ein Modell mit zwei enites, Person und Name, an, wobei jede Person eine Eins-zu-Eins-Beziehung mit einem Namen hat und die Personenbeziehung des Namens nicht optional ist. Zuvor in -awakeFromInsert Methode der Person, würde ich automatisch einen Namen Einheit für die neue Person erstellen:So richten Sie die Core Data-Beziehung automatisch ein, wenn verschachtelte Kontexte verwendet werden

- (void)awakeFromInsert 
{ 
    [super awakeFromInsert]; 

    NSManagedObjectContext *context = [self managedObjectContext]; 
    self.name = [NSEntityDescription insertNewObjectForEntityForName:@"Name" inManagedObjectContext:context]; 
} 

Dies funktioniert gut in einem einzigen, nicht-verschachtelten Objektkontext verwaltet. Wenn der Kontext jedoch einen übergeordneten Kontext hat, wird beim Speichern des untergeordneten Kontexts ein neues Person-Objekt im übergeordneten Kontext erstellt, und -awakeFromInsert wird erneut für dieses neue Objekt aufgerufen, bevor die Eigenschaften und Beziehungen der ursprünglichen Person kopiert werden. Also wird ein anderes Name-Objekt erstellt und dann "getrennt", wenn die bestehende Namensbeziehung kopiert wird. Das Speichern schlägt fehl, da die Überprüfung der jetzt nichtig Beziehung des schwebenden Namens fehlschlägt. Dieses Problem wird here sowie an anderen Stellen beschrieben.

Bis jetzt konnte ich keine gute Lösung für dieses Problem finden. Das mühsame Erstellen der Beziehung in der Getter-Methode verursacht das gleiche Problem, da der Getter von der internen Core Data-Maschine aufgerufen wird, wenn die neue Person im übergeordneten Kontext erstellt wird.

Die einzige Sache, die ich finden kann, besteht darin, auf die automatische Beziehungsgenerierung zu verzichten und die Beziehung immer entweder in der Controller-Klasse, die die Person erstellt, oder in einer Komfortmethode (z. B. +[Person insertNewPersonInManagedObjectContext:]), die nur von my aufgerufen wird, explizit zu erstellen Code und ist immer die Methode, die verwendet wird, um ein neues Person-Objekt explizit zu erstellen. Vielleicht ist dies die beste Lösung, aber ich möchte nicht so streng sein, nur eine einzige Methode zum Erstellen verwalteter Objekte zuzulassen, wenn andere Erstellungsmethoden, über die ich keine Kontrolle habe und deren Verwendung ich nicht kann check for/exclude, exist. Zum einen bedeutet dies, dass mehrere NSArrayController-Unterklassen die Art und Weise anpassen, wie sie verwaltete Objekte erstellen.

Hat jemand anderes, der auf dieses Problem gestoßen ist, eine elegante Lösung gefunden, die es einem NSManagedObject ermöglicht, ein Beziehungsobjekt beim Erstellen/Einfügen automatisch zu erstellen?

+0

Lassen Sie uns die Beziehung annehmen, optional werden. Dann würden Sie ein verwaistes Name-Objekt und zwei "richtige" Name-Objekte in ihrem jeweiligen Kontext haben. Es wäre interessant: 1. herauszufinden, ob die "richtigen" Objekt-IDs noch identisch sind (sie werden später sein, aber für den Moment sind sie temporär) und 2. wenn die ID des verwaisten Name-Objekts anders ist. Wenn dies der Fall ist, könnten Sie auch wakeFromInsert für Name implementieren, so dass es im untergeordneten Kontext nach einem Geschwister sucht und sich selbst löscht, wenn es keinen findet. Es würde dann vor dem Speichern aus dem Kontext entfernt werden und hoffentlich vor der Validierung ... – Toastor

+0

Hmm Ich denke, ich könnte falsch liegen - da an dem Punkt, an dem der verwaiste Name erstellt wird, der Kindkontext bereits gespeichert wurde, die IDs für die " Richtige "Namensobjekte" dürfen mich im Moment anders sehen, da ein Name gespeichert wurde und der andere noch temporär ist. Dennoch könnte es sich lohnen, es zu überprüfen ... – Toastor

Antwort

1

Ich ging mit der Komfort-Methode Lösung gehen. Alle NSManagedObject-Unterklassen in meiner App haben eine +insertInManagedObjectContext:-Methode. Erstellen von Instanzen dieser Objekte (in meinem eigenen Code) ist immer getan mit dieser Methode. Innerhalb dieser Methode kann ich dies:

+ (instancetype)insertInManagedObjectContext:(NSManagedObjectContext *)moc 
{ 
    MyManagedObject *result = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntityName" inManagedObjectContext:moc] 
    [result awakeFromCreation]; 
    return result; 
} 

- (void)awakeFromCreation 
{ 
    // Do here what used to be done in -awakeFromInsert. 
    // Set up default relationships, etc. 
} 

Was die Frage NSArrayController, zu lösen, dass gar nicht so schlecht ist. Ich habe einfach eine Unterklasse von NSArrayController, overrode -newObject, und verwendet diese Unterklasse für alle relevanten NSArrayControllers in meiner App:

@implementation ORSManagedObjectsArrayController 

- (id)newObject 
{ 
    NSManagedObjectContext *moc = [self managedObjectContext]; 
    NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName] 
               inManagedObjectContext:moc]; 
    if (!entity) return nil; 

    Class class = NSClassFromString([entity managedObjectClassName]); 
    return [class insertInManagedObjectContext:moc]; 
} 

@end 
1

Der erste Gedanke, den Sinn kommt, ist, dass während Name ‚s person Beziehung nicht optional ist, können Sie nicht sagen, dass Person‘ s name Beziehung auch nicht optional. Ist es in Ordnung, ein Person ohne Name zu erstellen, mit Ihrem Code zu entsorgen und dann das Name später zu erstellen, wenn Sie es wirklich brauchen?

Wenn nicht, eine einfache Möglichkeit, nur um zu überprüfen, ob Sie auf dem Stammkontext sind, bevor Sie eine Name erstellen:

- (void)awakeFromInsert 
{ 
    [super awakeFromInsert]; 

    NSManagedObjectContext *context = [self managedObjectContext]; 
    if ([context parentContext] != nil) { 
     self.name = [NSEntityDescription insertNewObjectForEntityForName:@"Name" inManagedObjectContext:context]; 
    } 
} 

Aber das funktioniert nur, wenn Sie immer neue Instanzen erstellen, die auf ein Kind Kontext und Sie nie Nest Kontexte mehr als eine Ebene tief.

Ich würde wahrscheinlich stattdessen eine Methode wie die insertNewPersonInManagedObjectContext: erstellen, die Sie beschreiben. Dann ergänzen Sie es mit etwas wie dem Folgenden, um Fälle zu behandeln, in denen Instanzen für Sie erstellt werden (d. H. Array-Controller):

- (void)willSave 
{ 
    if ([self name] == nil) { 
     NSManagedObjectContext *context = [self managedObjectContext]; 
     Name *name = [NSEntityDescription insertNewObjectForEntityForName:@"Name" inManagedObjectContext:context]; 
     [self setName:name]; 
    } 
} 

...und natürlich nicht mit einer benutzerdefinierten awakeFromInsert ...

+0

Danke für die Antwort Tom. Ich habe nicht angegeben, aber die Namensbeziehung der Person ist tatsächlich nicht optional. (Person/Name ist ein illustrativer Fall, meine tatsächlichen Entitäten sind anders.) Eine Überprüfung des Kontexts in '-awakeFromInsert' ist eine interessante Idee. Im Moment habe ich nur 2 Kontexte, aber es besteht eine gute Chance, dass ich einen Kontext der dritten Ebene möchte, ein Kind des aktuellen Kindes später. Ich denke, dein letzter Vorschlag ist ziemlich gut. Aber in Wirklichkeit erwartet die Benutzeroberfläche, dass es zu jeder Person eine Namensbeziehung gibt, da das Bearbeiten von "Namen" eine der Hauptaufgaben der App ist. Vielleicht benutzerdefinierte Array-Controller ist es ... –

+0

Dies ist einer der Gründe, die ich immer noch das Muster von mehreren unabhängigen Kontexten verschachtelten Eltern/Kind-Kontexte bevorzugen. Wenn es hier keine Eltern/Kind-Beziehung gäbe, würde Ihr 'watchFromInsert' nur einmal aufgerufen, und ein Aufruf von' mergeChangesFromContextDidSaveNotification: 'würde sicherstellen, dass andere Kontexte die neuen Daten erhalten. Wenn es sich nicht um benutzerdefinierte Array-Controller handelt, müssen Sie möglicherweise eine Bereinigungsmethode hinzufügen, die vor dem Speichern aufgerufen wird, um "Dangling' Name "-Instanzen zu finden und zu entfernen. –

Verwandte Themen