Philip007
Philip007

Reputation: 3230

Core Data: why I can't fetch anything with my predicate

Is there something wrong with the code? I can fetch nothing using this predicate. Comment out the predicate then I can fetch all objects from entity "BankDetail". So I think problem resides in these two lines.

// self.bankInfo.name is set in prepareForSegue in first view controller
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"info.name = %@",self.bankInfo.name];
[request setPredicate:predicate];

My model includes two entities, which are in one-to-one relationship

BankInfo.h

@class BankDetail;

@interface BankInfo : NSManagedObject

@property (nonatomic, retain) NSString * city;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * state;
@property (nonatomic, retain) BankDetail * detail;

@end

BankDetail.h

@class BankInfo;

@interface BankDetail : NSManagedObject

@property (nonatomic, retain) NSString * closeDate;
@property (nonatomic, retain) NSString * updateDate;
@property (nonatomic, retain) NSString * zip;
@property (nonatomic, retain) NSString * acquiringInstitution;
@property (nonatomic, retain) BankInfo * info;

@end

EDIT:

To provide more detail:

Get this in console:

info = "(<NSRelationshipDescription: 0x6d3eb30>), name info, isOptional 1, isTransient 0, entity BankDetail, renamingIdentifier info, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}, destination entity BankInfo, inverseRelationship detail, minCount 1, maxCount 1, isOrdered 0, deleteRule 2";

EDIT2: Turn out there's nothing wrong with the predicate. The bug is caused by a careless mistake elsewhere (see the accepted answer, it's about renaming). Please IGNORE this post if you have question about predicate.

Upvotes: 1

Views: 1296

Answers (7)

Rory O&#39;Bryan
Rory O&#39;Bryan

Reputation: 1904

The predicate code you originally posted is correct. Below is a quick test project that tests the basic principle: (correctly logs "bank name")

// Xcode 4.5:
// Create a new iOS Master-Detail project called "BankInfoTest" tick the "Use Core Data" check box (and ARC).
// Delete all files except the AppDelegate and the BankInfoTest data model (leave supporting files).
// Delete all properties and methods from AppDelegate.h
// Paste this code into AppDelegate.m
// Delete the default entity from the data model
// Add "BankInfo" entity, add attribute "name" of type string.
// Add "BankDetail" entity, add relationship called "info" to BankInfo (leave default 1 to 1 type).
// On BankInfo entity add inverse relationship "detail" to BankDetail (leave default 1 to 1 type).
// Select both entities then select "Editor -> Create NSManagedObject Subclass" to create subclasses.
// Run.

#import "AppDelegate.h"
#import "BankDetail.h"
#import "BankInfo.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    /////////////////////////////////////////////////////////////////////////////////////
    // Setup the core data stack.
    /////////////////////////////////////////////////////////////////////////////////////
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"BankInfoTest" withExtension:@"momd"];
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *appDocsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *storeURL = [appDocsDirectory URLByAppendingPathComponent:@"BankInfoTest"];

    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:nil];

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    context.persistentStoreCoordinator = coordinator;


    /////////////////////////////////////////////////////////////////////////////////////
    // Load some test data and reset the context.
    /////////////////////////////////////////////////////////////////////////////////////
    BankDetail *detailObject = [NSEntityDescription insertNewObjectForEntityForName:@"BankDetail" inManagedObjectContext:context];
    BankInfo *infoObject = [NSEntityDescription insertNewObjectForEntityForName:@"BankInfo" inManagedObjectContext:context];

    infoObject.name = @"bank name";
    detailObject.info = infoObject;

    [context save:nil];
    [context reset];

    /////////////////////////////////////////////////////////////////////////////////////
    // Test the Predicate
    /////////////////////////////////////////////////////////////////////////////////////
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"BankDetail"];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"info.name = %@", @"bank name"];
    [request setPredicate:predicate];

    NSArray *results = [context executeFetchRequest:request error:nil];
    BankDetail *fetchedObject = [results lastObject];
    NSLog(@"%@", fetchedObject.info.name);

    return YES;
}

@end

Upvotes: 0

Philip007
Philip007

Reputation: 3230

I finally fix the bug. It's not about the predicate at all. I rename the entity from BankDetails to BankDetail in .xcdatamodeld in Xcode editor by simply hit return key and change it, like renaming any other file.

Then I go ahead to manually rename various parts in the auto generated NSManagedObject subclass files, and in other class files that reference it, until all warnings go away. I think I have renamed all that are necessary, but I wasn't. The program doesn't run as I expect like I described in the question, but no error, no warning, Xcode just compile and run.

After some time, when I finally try to regenerate the subclass files to fix the bug, the old BankDetails shows up as class name, and lots of naming errors come out. I thought it was some bug of Xcode at first. So I clean the build and regenerate subclass files again. Yet, it's still the good old BankDetails. After a few attempts, I found the problem in Data Model Inspector (see screenshot below). Change the Class name and everything runs perfectly.

Moral: always rename in extreme caution!

enter image description here

Upvotes: 0

Rory O&#39;Bryan
Rory O&#39;Bryan

Reputation: 1904

Your predicate structure is OK. See my other answer for a simple project to prove the basic principle.

Based on responses to other answers it sounds like you are passing this NSFetchRequest to a NSFetchedResultsController and using its behavior to determine that your predicate doesn't work.

My first suggestion would be to log the predicate and see if it is as expected:

NSLog(@"%@", predicate);

If that is OK, then in order to test the theory that the predicate is not working you should execute the NSFetchRequest directly immediately after building the request, and inspect the results e.g.

// self.bankInfo.name is set in prepareForSegue in first view controller
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"info.name = %@",self.bankInfo.name];
[request setPredicate:predicate];

NSArray *results = [myMangedObjectContext executeFetchRequest:request error:nil];

If that doesn't shed light on the issue then I would suggest the that you remove the predicate from the request, execute the request, and use the following log statements to inspect the full results:

NSLog(@"%@", self.bankInfo.name);
NSLog(@"%@", [results valueForKeyPath:@"info.name"]);

The last log statement actually logs an array of all the bank names so you can see if the value in self.bankInfo.name is present in the full results as you expect.

Upvotes: 1

ChrisH
ChrisH

Reputation: 4558

If it's a one-to-one relationship, and you already have the self.bankInfo.name string that you want to match, why don't you fetch the BankInfo object with that name and access that object's .detail property? That's the whole point of having a relationship, no?

However, your predicate should work. What is self.bankInfo though? Are you sure that self.bankinfo.name is a valid string?

Upvotes: 1

Breno Gazzola
Breno Gazzola

Reputation: 2162

There's nothing wrong with your predicate as far as I see. I have a very similar one in my app and it works fine:

@class Hospital;

@interface Patient : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) Hospital *hospital;

@end

And

@class Patient;

@interface Hospital : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSSet *patients;

@end

My predicate:

request.predicate = [NSPredicate predicateWithFormat:@"hospital.name = %@", self.hospital.name];

So there's nothing wrong with your code. The problem must be because either self.bankInfo or self.bankInfo.name are null, or because there's nothing in info in your BankDetail object (the relationship was not set).

Upvotes: 0

Rory O&#39;Bryan
Rory O&#39;Bryan

Reputation: 1904

The problem may be the use of the = operator in the predicate string.

According to the documentation the LIKE operator is used for string comparisons, e.g.

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"info.name like %@",self.bankInfo.name];

The documentation also gives an example of traversing a relationship in the predicate so you should be able to achieve your desired result. From the NSPredicate documentation:

You can create predicates for relationships, such as:

  • group.name like "work*"

Upvotes: -1

Philip007
Philip007

Reputation: 3230

Finally I have to use a not-so-elegant workaround. I end up adding a name attribute for BankDetail too. So one BankInfo.name corresponds to one BankDetail.name. Then I use predicate:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",self.bankInfo.name];

And it does what I want: fetch one BankDetail object for corresponding BankInfo object. I wonder it's the best practice at all..

Upvotes: 0

Related Questions