Problem mit NSPredicate für 1:n Relation

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Problem mit NSPredicate für 1:n Relation

    Hallo liebe Community!

    jetzt wollte ich doch nochmal fragen, ob mir jemand einen guten Tipp
    zu meinem Problem geben könnte.

    Ich habe in meinem Datenmodell eine Entity Produkt, die jeweils zu genau
    einer Kategorie zugeordnet werden kann und somit Kategorien, die auf
    mehrere Produkten verweisen können.

    Nun bekomme ich eine Kategorie übergeben und möchte mir alle Produkte
    auf die diese Kategorie verweist anzeigen lassen.

    Verwende hierfür einen NSFetchedResultscontroller und möchte
    somit ein NSPredicate formulieren, welches diesen Zweck erfüllt.

    Ermittle hierfür aus der Kategorie die ObjectIDs der Produkte

    Quellcode

    1. NSArray *produktOIDs = [kategorie valueForKeyPath:@"produkte.objectID"];

    Und generiere mir dann ein Predicate

    Quellcode

    1. activeFilter = [NSString stringWithFormat: @"SELF IN %@", produktOIDs];


    Quellcode

    1. fetchRequest.predicate = [NSPredicate predicateWithFormat:activeFilter];

    Doch leider crasht der Fetch mit:

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "SELF IN {(
    0x5e39ee0 <x-coredata://5B4EB519-E1AB-43D2-97C5-4391BC6895ED/Produkt/p113>,
    0x5e39ec0 <x-coredata://5B4EB519-E1AB-43D2-97C5-4391BC6895ED/Produkt/p37>,
    0x5e39ed0 <x-coredata://5B4EB519-E1AB-43D2-97C5-4391BC6895ED/Produkt/p103>

    Würde mich über einen Tipp sehr freuen.

    Thanks,
    Matthias
  • Warum benutzt Du einen NSFetchedResultscontroller für diesen Zweck? Alle Produkte einer Kategorie bekommst Du ja schon einfach so:

    Quellcode

    1. NSSet *produkte = [kategorie valueForKey:@"produkte"];

    Das NSSet kannst Du jetzt in ein sortiertes NSArray wandeln und als Basis für die Datasource benutzen.

    Michael
  • Hi und thanks für den Tipp,
    ich verwende den NSFetchedResultscontroller für diese TableView
    da Editing, adding ... via dieser TableView aktiv sind und er
    die ganzen Updates ... managed.
    Ausserdem ist diese Variante mit der Auswahl eine Kategorie,
    nur eine Suchvariante von vielen und ich würde das Handling
    gerne beibehalten.
    Die Idee mit dem @"SELF IN %@", produktOIDs
    stammt übrigens aus dem Core Data Programming Guide,
    mir will nur nicht auffallen, was an dem predicate nicht
    stimmt und der Fetch somit crast ?!?
    Liegts evtl. an der Ermittlung der ObjectIDs ?
    What am I doing wrong ?
    Thx
    Matthias
  • Der Vergleich via = schlägt ebenso fehl,
    liegt wohl daran, dass die Entity Kategorie ne 1:n Relation zu den Produkten hat und
    somit ein SET zurückgeliefert wir -> Vergleich mit = ist somit nicht möglich !
    Leider funktioniert das Org. Apple Docu Beispiel:
    @"SELF IN %@", produktOIDs auch nicht.
    Wenn noch jemand ne Idee hat - gerne !
    Viele Grüße
    Matthias
  • Wie baust Du denn den Fetch-Request genau zusammen? Ich hatte mir das eigentlich so vorgestellt:

    Quellcode

    1. NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
    2. [fetchRequest setEntity:[NSEntityDescription entityForName:@"Produkt" inManagedObjectContext:self.managedObjectContext]];
    3. [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"kategorie = %@", kategorie]];


    Michael
  • Das entspricht meiner Vorgehensweise

    Quellcode

    1. NSString *predicate = [NSString stringWithFormat:@"%@", filter ];
    2. fetchRequest.predicate = [NSPredicate predicateWithFormat:predicate];

    wobei der filter für diese Suche auf "kategorie = %@", kategorie" steht.

    Die Ermittlung des Vergleichsoperators (Kategorie) erfolgt in einem gesonderter Auswahlcontroller via didSelectRowAtIndexPath
    und sieht bei mir so aus:

    Quellcode

    1. Kategorie *kategorie = (Kategorie *)[fetchedResCtrl_Kategorien objectAtIndexPath:indexPath];

    Diese kategorie wird dann zurückgegeben, in den filter übernommen und das Predicate crasht.

    PS: das generelle Handling funktioniert ja, wenn ich in dieser Mimik z.B den filter "kategorie.name = 'XY' verwende
    läuft alles prima, nur mit dem Vergleich auf die zurückgegebene Entity kategorie treten die Probleme auf !?!

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von mackasper ()

  • mackasper schrieb:

    Das entspricht meiner Vorgehensweise

    Quellcode

    1. NSString *predicate = [NSString stringWithFormat:@"%@", filter ];
    2. fetchRequest.predicate = [NSPredicate predicateWithFormat:predicate];

    wobei der filter für diese Suche auf "kategorie = %@", kategorie" steht.

    Also, wenn filter wirklich so aussehen sollte, dann würde in Zeile 2 ja dann ein Parameter fehlen, also die würde dann so aussehen:

    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"kategorie = %@"];

    Das muss ja abschmieren.
    Aber mal zum Ursprungsproblem zurück. Ich habe mir das mal genauer angeschaut. Mich hat es ja gewundert, warum Du immer erst mit stringWithFormat: einen „Zwischenstring“ erzeugst, und mit diesem dann erst die Methode predicateWithFormat: fütterst. Das ist ja nur unnötiger, zusätzlicher Aufwand, aber ich dachte mir, soll er doch, wenn er will. ;) Aber falsch gedacht. Genau das ist das Problem! Wenn nämlich im Format-String der Platzhalter %@ durch eine Collection (NSArray, NSSet, NSDictionary) ersetzt werden soll, führen die beiden Methoden zu unterschiedlichen Ergebnissen:

    String Version

    Quellcode

    1. name IN {(
    2. Anton,
    3. Berta,
    4. Carsten
    5. )}

    Predicate Version

    Quellcode

    1. name IN {"Anton", "Berta", "Carsten"}

    Mit dem Format, welches NSString da abliefert kann NSPredicate halt nichts anfangen. Vergiss einfach diesen Zwischenschritt über NSString. Somit dürfte die Lösung Deines Problems sein:

    Quellcode

    1. NSArray *produktOIDs = [kategorie valueForKeyPath:@"produkte.objectID"];
    2. fetchRequest.predicate = [NSPredicate predicateWithFormat:@"objectID IN %@", produktOIDs];

    Das 'SELF' im Format-String dürfte hier auch nicht richtig sein, denn Du willst ja die objectIDs vergleichen.

    Michael
  • Bingo, das mit dem Zwischenstring war tatsächlich das Problem !
    Jetzt bin ich nur noch gespannt, wie die Performance bei grossen
    Kategorien aussieht (muss mal eine mit 1000 Produkten) generieren...
    Müsste ja ein riesen Predicate werden - bin mal gespannt wie
    das intern gehandelt / gecached / aufgelöst wird ..
    1000 Thx !!!
    Matthias
  • Müsste ja ein riesen Predicate werden

    ... was darauf schließen läßt, dass dieser Ansatz ein sehr schlechter ist.

    Du machst den Fehler ganz am Anfang, indem Du einen FetchResultsController benutzt, wo doch schlicht dieses
    NSMutableSet *produkte = [kategorie mutableSetValueForKey:@"produkte"];

    genügen würde, sofern Du Produkte der Kategorie hinzufügen oder entfernen möchtest, oder
    NSSet *produkte = [kategorie valueForKey:@"produkte"];

    genügen würde, wenn Du die Produkte dieser Kategorie nicht verändern willst.

    Teste ruhig Deine FetchReqeust mit 1000 Predicates...

    No.
  • Das hatte ich mir fast gedacht, die grosse Frage die sich mir nur stellt ist,
    ob bei dieser Variante tatsächlich nur die ObjectIDs für den Vergleich verwendet,
    denn die Ausgabe für

    Quellcode

    1. NSPredicate * predicate = [NSPredicate predicateWithFormat:kategorie = %@", kategorie];
    2. NSLog(@"predicate=%@", predicate);

    liefert bei mir auch das gesamte (riesen) Object kategorie zu Tage ?!?
    Sollte man tatsächlich das predicate noch auf den explizieten Vergleich
    der ObjectIDs runterbrechen müssen, um die optimale Performance zu erhalten ?
    Gruß
    Matthias
  • Also, Dein Ziel ist doch, dass Dir der NSFetchedResultsController alle Produkte holt, die einer vorgegebenen Kategorie zugeordnet sind. Dann gehen wir doch mal beide Varianten durch.
    1. Deine Variante

      Quellcode

      1. NSArray *produktOIDs = [kategorie valueForKeyPath:@"produkte.objectID"];
      2. fetchRequest.predicate = [NSPredicate predicateWithFormat:@"objectID IN %@", produktOIDs];
      Zunächst erstellst Du ein Array mit allen Objekt-IDs der Produkte, die der vorgegebenen Kategorie zugeordnet. Dann sagst Du dem NSFetchedResultsController, dass er alle Produkte holen soll, deren Objekt-ID in der Liste der Objekt-IDs enthalten ist. Du erhältst also das gewünschte Ergebnis.

    2. Die bessere Variante

      Quellcode

      1. fetchRequest.predicate = [NSPredicate predicateWithFormat:@"kategorie = %@", kategorie];
      Hier sagt man dem NSFetchedResultsController direkt, hole mir alle Produkte, die der vorgegebenen Kategorie zugeordnet sind. Du erhältst das gewünschte Ergebnis. Es ist identisch mit dem der ersten Variante.

    Schauen wir uns mal die beiden Varianten mal aus der Performance-Sicht an. In Variante 1 muss erst einmal eine Liste mit Objekt-IDs erstellt werden, was in Variante 2 komplett entfällt. Punkt für Variante 2. Dann muss der NSFetchedResultsController in der Variante 1 alle Produkte durchgehen und jeweils nachschauen, ob die Objekt-ID in der zuvor erstellten Liste enthalten ist. Je länger die Liste ist, desto länger wird das dauern. In der 2. Variante muss der NSFetchedResultsController auch alle Produkte durchgehen, aber er braucht nur nachschauen, ob die Kategrie zur vorgegebenen Kategorie passt. Noch ein Punkt für Variante 2.

    Ich würde sagen, ohne großartige Messungen gemacht zu haben, ist Variante 2 klarer Punktsieger. Variante 1 ist irgendwie von hinten durch die Brust ins Auge.

    Michael
  • Da bin ich ganz deiner Meinung, ich war nur etwas überrascht,
    dass die Ausgabe des predicates für Variante 2

    Quellcode

    1. NSPredicate * predicate = [NSPredicate predicateWithFormat:kategorie = %@", kategorie];
    2. NSLog(@"predicate=%@", predicate);

    auch die ganze kategorie ausgibt - incl. aller enthaltenen refs auf die Produkte !
    Würde dies im Umkehrschluss nicht auch bedeuten, dass
    das Predicate (V2) auch mit riesigen kategorien riesig werden würde ?
    Oder ist's nur ein Ausgabeproblem ?
    Und löst nur die Ausgabe via NSLog intern die kategorie weiter auf und
    das tatsächliche Predicate wäre wie gewünscht die kurze Variante ?
    (ohne die ganzen refs auf die Produkte)
    Matthias
  • mackasper schrieb:

    Da bin ich ganz deiner Meinung, ich war nur etwas überrascht,
    dass die Ausgabe des predicates für Variante 2

    Quellcode

    1. NSPredicate * predicate = [NSPredicate predicateWithFormat:kategorie = %@", kategorie];
    2. NSLog(@"predicate=%@", predicate);

    auch die ganze kategorie ausgibt - incl. aller enthaltenen refs auf die Produkte !

    Der Platzhalter %@ wird durch den string, den descriptionWithLocale:, wenn verfügbar, sonst description liefert ersetzt. Es hängt nun davon ab, wie diese Methode(n) in NSPredicate implementiert sind.

    mackasper schrieb:

    Würde dies im Umkehrschluss nicht auch bedeuten, dass
    das Predicate (V2) auch mit riesigen kategorien riesig werden würde ?
    Oder ist's nur ein Ausgabeproblem ?
    Und löst nur die Ausgabe via NSLog intern die kategorie weiter auf und
    das tatsächliche Predicate wäre wie gewünscht die kurze Variante ?
    (ohne die ganzen refs auf die Produkte)
    Matthias

    Ich weiß nicht, wie NSPredicate intern arbeitet. Kann durchaus sein, dass das Predicate trotzdem so groß wird. Wenn Du Speicher sparen willst, bleibt Dir wohl nur die Variante, die Produkte ohne Fetch-Request direkt aus der Kategorie zu holen. Dürfte auch die schnellste Variante sein.

    Michael