Reputation: 3230
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:
self.bankInfo.name
is definitely set, I NSLog it right before the line of predicate
And I do this in viewDidLoad
:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"BankDetail" inManagedObjectContext:context];
NSLog(@"[entity description] is %@",[entity description]);
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
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
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!
Upvotes: 0
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
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
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
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
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