0
votes

I am using an NSFetchedResultsController to load messages for different email accounts into a tableview and a collection view. Everything works great on iOS7, and everything works great everywhere in iOS6 EXCEPT when I use a combined OR predicate.

Now I know that an NSFetchedResultsController will return the same item twice if it matches multiple of these predicates. My problem is that the message entity ONLY MATCHES ONE OF THESE PREDICATES yet is being returned the same number of times as predicates.

So for some reason the NSFetchedResultsController is saying these messages pass all the predicates, but only on iOS6. It is doing this for every single message from a particular email account, but not for messages from the other accounts.

If I adjust the OR predicate to go off of ObjectIDs instead, unique results are returned. However my UI will no longer update properly as values are adjusted

For the life of me I can't seem to figure out how on earth these entities are passing the predicates for other email accounts and folders.

My predicate construction that results in duplicates is as follows. Account -> messages is a oneToMany relationship, folders <-> messages is a manyToMany.

                NSMutableArray *predicates = [NSMutableArray array];
                for (CRMFolder *folder in comboFolder.folders) {
                    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isMarkedForDelete != YES AND isTopReferenceMessage = YES AND folders contains %@ AND account = %@", folder, folder.account];
                    [predicates addObject:predicate];
                }
                fetchRequest.predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];

So if I change that to objectIDs like so, I get unique results:

                NSMutableArray *predicates = [NSMutableArray array];
                for (CRMFolder *folder in comboFolder.folders) {
                    NSArray *objectIDs = [folder.messages valueForKey:@"objectID"];
                    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isMarkedForDelete != YES AND isTopReferenceMessage = YES AND self in %@", objectIDs];
                    [predicates addObject:predicate];
                }
                fetchRequest.predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];

But then if I combine these two, I STILL GET DUPLICATES.

                NSMutableArray *predicates = [NSMutableArray array];
                for (CRMFolder *folder in comboFolder.folders) {
                    NSArray *objectIDs = [folder.messages valueForKey:@"objectID"];
                    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isMarkedForDelete != YES AND isTopReferenceMessage = YES AND folders contains %@ AND account = %@ AND self in %@", folder, folder.account, objectIDs];
                    [predicates addObject:predicate];
                }
                fetchRequest.predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];

Any ideas On

1) Why are these entities passing predicates they shouldn't?

2) How come this only occurs on iOS6?

3) Whats the fix?

I know I can use returnsDistinctResults, but that requires converting everything to dictionaries, which currently isn't an option.

EDIT

I just did a test, and if I exclude the account creating the issues, results are normal. Similarly I just excluded all other accounts, and no duplicates. So it is only when these predicates all combine this occurs. I'm really suspect of some bug in the fetched results controller.

EDIT 2

Here is the raw CoreData Log for a fetch that produces several duplicates. Note there are 3 messages that it duplicates 9 times to get 27 results.

CoreData: annotation: sql connection fetch time: 0.0528s CoreData: sql: SELECT t0.Z_PK, t1.Z_14BLINDCARBONCOPYMESSAGES FROM Z_3BLINDCARBONCOPYMESSAGES t1 JOIN ZCRMADDRESS t0 ON t0.Z_PK = t1.Z_3BLINDCARBONCOPIES WHERE t1.Z_14BLINDCARBONCOPYMESSAGES IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.Z_14BLINDCARBONCOPYMESSAGES ASC CoreData: annotation: sql execution time: 0.0004s CoreData: annotation: Prefetching from join table for many-to-many relationship "blindCarbonCopies" from database. Got 0 rows CoreData: sql: SELECT t0.Z_PK, t1.REFLEXIVE FROM Z_14REFERENCEMESSAGES t1 JOIN ZCRMMESSAGE t0 ON t0.Z_PK = t1.Z_14REFERENCEMESSAGES WHERE t1.REFLEXIVE IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.REFLEXIVE ASC CoreData: annotation: sql execution time: 0.0525s CoreData: annotation: Prefetching from join table for many-to-many relationship "referenceMessages" from database. Got 0 rows CoreData: sql: SELECT t0.Z_PK, t1.Z_14CARBONCOPYMESSAGES FROM Z_3CARBONCOPYMESSAGES t1 JOIN ZCRMADDRESS t0 ON t0.Z_PK = t1.Z_3CARBONCOPIES WHERE t1.Z_14CARBONCOPYMESSAGES IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.Z_14CARBONCOPYMESSAGES ASC CoreData: annotation: sql execution time: 0.0270s CoreData: annotation: Prefetching from join table for many-to-many relationship "carbonCopies" from database. Got 0 rows CoreData: sql: SELECT t0.Z_PK, t1.Z_14SENDERMESSAGES FROM Z_3SENDERMESSAGES t1 JOIN ZCRMADDRESS t0 ON t0.Z_PK = t1.Z_3SENDERS WHERE t1.Z_14SENDERMESSAGES IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.Z_14SENDERMESSAGES ASC CoreData: annotation: sql execution time: 0.0007s CoreData: annotation: Prefetching from join table for many-to-many relationship "senders" from database. Got 6 rows CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZEMAIL, t0.ZLABEL, t0.ZNAME, t0.ZPRIMARYADDRESS, t0.ZPROFILEPHOTO, t0.ZPROFILEPHOTOLASTCACHEDATE, t0.ZPROFILEPHOTOSOURCE, t0.ZCONTACT, t0.ZUSER FROM ZCRMADDRESS t0 WHERE t0.Z_PK IN (?,?,?)
CoreData: annotation: sql connection fetch time: 0.0004s CoreData: annotation: total fetch execution time: 0.0007s for 3 rows. CoreData: annotation: Prefetching with key 'senders'. Got 3 rows. CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZCONTENTID, t0.ZCONTENTTYPE, t0.ZDATA, t0.ZDATE, t0.ZEXCHANGEID, t0.ZFILEEXTENSION, t0.ZISLOCAL, t0.ZISTEMPORARY, t0.ZORIGINALFILENAME, t0.ZSIZEINBYTES, t0.ZTHUMBNAIL, t0.ZMESSAGE, t0.ZUSER FROM ZCRMATTACHMENT t0 WHERE t0.ZMESSAGE IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZMESSAGE CoreData: annotation: sql connection fetch time: 0.0005s CoreData: annotation: total fetch execution time: 0.0007s for 0 rows. CoreData: annotation: Prefetching with key 'attachments'. Got 0 rows. CoreData: sql: SELECT t0.Z_PK, t1.Z_14REPLYTOMESSAGES FROM Z_3REPLYTOMESSAGES t1 JOIN ZCRMADDRESS t0 ON t0.Z_PK = t1.Z_3REPLYTOS WHERE t1.Z_14REPLYTOMESSAGES IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.Z_14REPLYTOMESSAGES ASC CoreData: annotation: sql execution time: 0.0006s CoreData: annotation: Prefetching from join table for many-to-many relationship "replyTos" from database. Got 6 rows CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZEMAIL, t0.ZLABEL, t0.ZNAME, t0.ZPRIMARYADDRESS, t0.ZPROFILEPHOTO, t0.ZPROFILEPHOTOLASTCACHEDATE, t0.ZPROFILEPHOTOSOURCE, t0.ZCONTACT, t0.ZUSER FROM ZCRMADDRESS t0 WHERE t0.Z_PK IN (?,?,?)
CoreData: annotation: sql connection fetch time: 0.0004s CoreData: annotation: total fetch execution time: 0.0006s for 3 rows. CoreData: annotation: Prefetching with key 'replyTos'. Got 3 rows. CoreData: sql: SELECT t0.Z_PK, t1.Z_14RECIPIENTMESSAGES FROM Z_3RECIPIENTMESSAGES t1 JOIN ZCRMADDRESS t0 ON t0.Z_PK = t1.Z_3RECIPIENTS WHERE t1.Z_14RECIPIENTMESSAGES IN ( 276, 276, 276, 276, 276, 276, 276, 276, 276, 286, 286, 286, 286, 286, 286, 286, 286, 286, 624, 624, 624, 624, 624, 624, 624, 624, 624 ) ORDER BY t1.Z_14RECIPIENTMESSAGES ASC CoreData: annotation: sql execution time: 0.0006s CoreData: annotation: Prefetching from join table for many-to-many relationship "recipients" from database. Got 6 rows CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZEMAIL, t0.ZLABEL, t0.ZNAME, t0.ZPRIMARYADDRESS, t0.ZPROFILEPHOTO, t0.ZPROFILEPHOTOLASTCACHEDATE, t0.ZPROFILEPHOTOSOURCE, t0.ZCONTACT, t0.ZUSER FROM ZCRMADDRESS t0 WHERE t0.Z_PK IN (?)
CoreData: annotation: sql connection fetch time: 0.0004s CoreData: annotation: total fetch execution time: 0.0007s for 1 rows. CoreData: annotation: Prefetching with key 'recipients'. Got 1 rows. CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZENCRYPTEDBODY, t0.ZENCRYPTEDHTMLBODY, t0.ZENCRYPTEDPLAINTEXTBODY, t0.ZENCRYPTEDSUBJECT, t0.ZMESSAGE FROM ZCRMMESSAGEBODY t0 WHERE t0.ZMESSAGE IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
CoreData: annotation: sql connection fetch time: 0.0011s CoreData: annotation: total fetch execution time: 0.0015s for 3 rows. CoreData: annotation: Prefetching with key 'messageBody'. Got 3 rows. CoreData: annotation: total fetch execution time: 0.1482s for 27 rows.

1
What do you mean by duplicate items? A FRC does not return the same elements twice.Martin R
Yes it does with multiple predicates. They are the exact same NSManagedObject, with the same permanent ID being returned multiple times by the NSFetchedResultsControllerJack Freeman
I've also checked the SQL DB and there is only one entryJack Freeman
It would be useful to turn on Core Data's SQLite debugging to see what exactly is going on, as described here: nshipster.com/launch-arguments-and-environment-variablesTom Harrington
folders contains ... is a wrong predicate usage. contains is only valid for string value matching.Dan Shelly

1 Answers

1
votes

As Dan Shelly noted, CONTAINS is incorrectly used in the predicate. I believe, as he explains, that it's only for finding a substring within another string. See the predicate documentation. But, for your purpose, you can use a subquery instead.

So, I'll jump ahead to the fix. You could change your subpredicate to the following:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isMarkedForDelete != YES AND isTopReferenceMessage = YES AND SUBQUERY(folders, $folder, $folder = %@).@count != 0 AND account = %@", folder, folder.account];

As Martin R. noted, a fetch request should not return the same element twice. I don't know why it would, if it does indeed, unless it's a result of using an invalid predicate.

Also, note that, since a fetch request should not return duplicates, the usage of returnsDistinctResults is a bit different than what it sounds like. You can find an example here.