I am very new to the Core Data programming. I have a question to which I hope to get some clarification.
Supposing I have an NSManagedObject called Company, with the following attributes:
companyName
companyEmail
companyPhoneNo
companyUserName
companyPassword
In this object the companyName attribute is indexed.
So, my question is, how can I make sure that there will be only entry with same companyName, companyEmail, companyPhoneNo, companyUserName and companyPassword?
Do I need to make a request to check if there are any records with the same attribute values or is a simple check with the object id sufficient?
Thanks.
Here's an example may be helps:
NSError * error;
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:NSStringFromClass([self class])
inManagedObjectContext:managedObjectContext]];
[fetchRequest setFetchLimit:1];
// check whether the entity exists or not
// set predicate as you want, here just use |companyName| as an example
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"companyName == %#", companyName]];
// if get a entity, that means exists, so fetch it.
if ([managedObjectContext countForFetchRequest:fetchRequest error:&error])
entity = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
// if not exists, just insert a new entity
else entity = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([self class])
inManagedObjectContext:managedObjectContext];
[fetchRequest release];
// No matter it is new or not, just update data for |entity|
entity.companyName = companyName;
// ...
// save
if (! [managedObjectContext save:&error])
NSLog(#"Couldn't save data to %#", NSStringFromClass([self class]));
Tip: countForFetchRequest:error: does not fetch entity actually, it just returns a number of entities that match the predicate you set before.
You have two options for maintaining you store with no duplicates:
Make fetch in insert.
Insert all the new data and then delete the duplicates before saving.
What is faster and more convenient? Presumably first way. But you'd better test it using Instruments and find the right way for your app.
Here are the docs on this question.
http://developer.apple.com/library/mac/ipad/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174-SW1
Related
And yes, I've set up all the proper entities in my xcdatamodelId. I've been using CoreData for months now and have never run into this problem just yet. Is there something else I'm possible missing?
This is my code to pull data.
- (NSArray *)getCDRuneSets {
NSError *error;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Rune" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *cdRuneSets = [context executeFetchRequest:fetchRequest error:&error];
return cdRuneSets;
}
And I have an entity named "Rune" in my xcdatamodelId. Any ideas on what I could possibly be doing wrong? :\
Edit:
I also have my AppDelegate run this piece of code to instantiate the my CoreDataBank's (It's a StaticSingleton) context
CoreDataBank *bank = [CoreDataBank getBank];
[bank setContext:[self managedObjectContext]];
Another possible solution to this incase anyone finds it... Make sure your entity name in your model matches your specified entity in entityForName
I had an s in my model (as in plural) and not in my code :(
I have an application that uses CoreData to store Customer entities. Each Customer entity has a customerName, customerID, and other properties.
I then display a list of all the Customers only showing their customerName and customerID.
I can do this fine by executing a fetch request to grab all the Customer entities, however I only need to show the customerName and customerID property.
Question 1:
I am trying to use the setPropertiesToFetch to specify only these properties yet every time it only returns 1 object in the array.
Here is what my method looks like:
NSFetchRequest *fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Customer" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSDictionary *entProperties = [entity propertiesByName];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:[entProperties objectForKey:#"customerName"],[entProperties objectForKey:#"customerID"], nil]];
[fetchRequest setFetchLimit:30];
NSError *error;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"fetched objects count = %d", [fetchedObjects count]);
if (error) {
NSLog(#"Error performing fetch = %#", [error localizedDescription]);
return nil;
} else {
NSLog(#"successful fetch of customers");
for( NSDictionary* obj in fetchedObjects ) {
NSLog(#"Customer: %#", [obj objectForKey:#"customerName"]);
}
return fetchedObjects;
}
The one object that is returned is ok so I know that is grabbing at least one Customer objects customerName and customerID. I just need it to return all the Customer objects customerName and customerID.
Question 2:
Is this the best way to be doing this? My thought is that there could be up to 10k+ Customer objects in the database. So it would be memory efficient to only fetch the needed properties to display in the table rather than the entire Customer object.
Then when a customer is selected in the table, I fetch the entire Customer object to display it's details.
However, I've read that it is also good practice to load an entire entity rather than just it's properties if the respective entity could be used in the near future. I guess the idea is that a single fetch request is better than two.
Thank you for your help.
For Question 1: here is my sample code written in swift. I continuously test the CoreData API, and find this calling way. Hope to help you.
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "Customer")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.propertiesToFetch = [
#keyPath(Customer.customerID),
#keyPath(Customer.customerName)
]
let customers: [NSDictionary]
do {
customers = try managedObjectContext.fetch(fetchRequest)
customers.forEach {
print("CustomerID: \($0["customerID"]), CustomerName: \($0["customerName"])")
}
} catch {
print(error.localizedDescription)
}
random,
Question 2:
It sounds like you are using fetch limits when you should be using batch limits. Batch limits are a basic cursoring mechanism. They are designed to handle memory efficiently.
W.r.t. Question 1, I'm a big believer in just writing the app in the natural way and then using Instruments and other tools to decide upon performance and memory optimizations. Try getting the app to run correctly first. Then make it fast.
Andrew
In my core data model, a Person has one or more Cars, specified by the unordered to-many relationship 'Cars'. Frequently, I need to retrieve a Person's cars ordered by datePurchased, or by dateLastUsed.
Until now, I have been adding my own method to Person for carsByDatePurchased. This uses a sort descriptor to sort the NSSet cars and return an NSArray.
Could/should I instead use a Fetched Property for this? I am experiencing some performance overhead using the sort descriptor every time I need the cars in a certain order, even going so far as implementing my own caching of carsByDatePurchased. It looks like the fetched property is cached for me - is that correct?
What are the limitations of a fetched property vs my own implementation?
And crucially, does the fetched property's value persist between executions? If I update the fetched property and save my context, is the value stored for the next time I launch the application?
A fetched property will work, and indeed I used it in my own project with a Post->Comment relationship which needs to be sorted by 'date added index'.
There are a number of caveats: You cannot specify a sort descriptor in the visual editor and have to specify it in code.
I use something like this
// Find the fetched properties, and make them sorted...
for (NSEntityDescription *entity in [_managedObjectModel entities])
{
for (NSPropertyDescription *property in [entity properties])
{
if ([property isKindOfClass:[NSFetchedPropertyDescription class]])
{
NSFetchedPropertyDescription *fetchedProperty = (NSFetchedPropertyDescription *)property;
NSFetchRequest *fetchRequest = [fetchedProperty fetchRequest];
// Only sort by name if the destination entity actually has a "index" field
if ([[[[fetchRequest entity] propertiesByName] allKeys] containsObject:#"index"])
{
NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:#"index"
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
}
}
}
}
In My Post entity I have a fetched property called "sortedComments" which is defined as:
post == $FETCH_SOURCE
where posts have a to-many "comments" relationship and comments have a "post" inverse
In opposition to the other answers here: The benefits of using a fetched property like this, is CoreData takes care of the caching and invalidating the cache as comments for a post or indeed the post that owns them changes.
If you want to gain some performance, do your fetch with an NSFetchedResultsController and have it working with a cache. Next time you perform the same fetch, the fetch will be faster. In your particular name, you will have to cache names. Take a look at the NSFetchedResultsController documentation.
A fetched property is basically a fetch request. I am not aware of ways to add sort descriptors to these properties in the GUI, but I may be wrong. But why not just create a fetch request in your carsByDatePurchased method and provide a sort descriptor? It returns an array or the results (which you can wrap cheaply in an NSOrderedSet with copyItems: flag set to no).
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"DataRecord" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *obj in fetchedObjects) {
NSLog(#"Name: %#", [obj valueForKey:#"name"]);
NSLog(#"Info: %#", [obj valueForKey:#"info"]);
NSLog(#"Number: %#", [obj valueForKey:#"number"]);
NSLog(#"Create Date: %#", [obj valueForKey:#"createDate"]);
NSLog(#"Last Update: %#", [obj valueForKey:#"updateDate"]);
}
NSManagedObject *obj = [fetchedObjects objectAtIndex:0];
[self displayManagedObject:obj];
selectedObject = obj;
Could any one tell me what's the wrong with this code? It raises the following error and cause application to crash:
reason: 'keypath Studies.patients.PatientName not found in entity <NSSQLEntity Studies id=3>'
Code:
- (void)viewDidLoad {
[super viewDidLoad];
test_coredataAppDelegate *appDelegate = (test_coredataAppDelegate *)[[UIApplication sharedApplication] delegate];
self.context = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Studies" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
/**/
NSLog(patientName);
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:
#"(Studies.patients.PatientName == %# )",patientName]];
NSError *error;
self.StudiessList = [_context executeFetchRequest:fetchRequest error:&error];
self.title = #"patients";
[fetchRequest release];
}
Firstly, since your fetch entity is Studies you don't include it in the predicate because the Studies objects are the ones receiving the predicate test in the first place. So your predicate should be at least just:
patients.PatientName == %#
However, by convention, patients would indicate a to-many relationship. If so, that means that the actual value of patients is a set of (presumably) Patient objects. As such you can't ask a set for an attribute value as above: Instead you have to ask for a new set of all object in the set that match the predicate. Use the ANY or All operator like so:
ALL patients.PatientName == %#
I would add that by convention all attribute and relationship names start with lower case letters so if PatientName is an attribute it should be patientName.
Either the Studies entity does not have a patient property, or whatever entity the patients relationship points to does not have a PatientName property (pay attention to upper/lowercase issues) or both.
Usually when I see this error it means that I added a new Version to my Model but forgot to update the Current Model Version for the project.
Posting this answer to help my future self because it's not the first time I have landed on this page!
In my case, I was referencing a superclass property of the entity but hadn't set the parent entity in the xcdatamodel file. I had to select the other NSManagedObject which I was subclassing from the inspector menu (default is None):
I have a UITableView that displays a subset of a large number of entities named "Documents". The subset is defined by another entity "Selection". Selections are named, ordered list of documents.
It Works fine, except when I want to change the displayed selection at run time. I get only a blank list.
Basically, I need to change the predicate that my NSFetchedResultsController holds so that the new predicate uses the another Selection. I couldn't make it work. My last attempt is to get rid of the NSFetchedResultsController altogether and reallocate it:
- (void) displaySelection:(Selection *)aSet
{
self.currentSelection = aSet;
self.fetchedResultsController = nil;
// methods here don't all use the property but directly the ivar, so we must trigger the getter
[self fetchedResultsController];
[self.tableView reloadData];
}
And of course, the NSFetchedResultsController getter does the right thing:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) { return fetchedResultsController; }
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setPredicate:predicate];
<snip>
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
<snip>
return fetchedResultsController;
}
This code works the first time, because the Initial Selection is set. When displaySelection: is called, though, the tableview becomes blank.
A very similar question was asked at NSFetchedResultsController fetch request - updating predicate and UITableView
And the answer was to get rid of the NSFetchedResultsController. I don't want to do that, because NSFetchedResultsController brings a lot of useful goodies here (eg caching, partial loading...). The question still stands: how to "switch" data in a UITableView backed by a NSFetchedResultsController, where "switch" means having a different predicate, or even (not in my case) a different entity.
Note for the sake of completeness, that since the many-to-many relationship from Selection to Document is ordered, it is handled through an in-between lightweight entity called DocInSelection, which has an "ordering" property and two many-to-one relationships to Document and Selection.
Thanks for any suggestion.
Since NSFetchedResultsController(FRC) is an object, you can store instances of it like any other object.
One useful technique is to initialize and store several FRC in a dictionary and then set the tableview controller's fetchedResultController attribute to the FRC you need at the moment. This is useful for situations such as having a segmented control to sort on different attributes or entities in the same table. This technique has the advantage of maintaining the individual FRC caches which can speed fetches up significantly.
Just make sure to send the tableview itself a beginUpdates before you swap controllers and then an endUpdates when you are done. This prevents the table from asking for data in the narrow window when the FRC are being swapped out. Then call reloadData.
After I posted my question, I tried a variant of the code the OP of the other question showed. It works for me. Here it is:
- (void) displaySelection:(Selection *)aSet
{
if (aSet != self.currentSelection) {
self.currentSelection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate = nil;
NSEntityDescription *entity = nil;
entity = [NSEntityDescription entityForName:#"DocInSelection" inManagedObjectContext:managedObjectContext];
predicate = [NSPredicate predicateWithFormat:#"selection.identifier like %#", currentSelection.identifier];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.tableView reloadData];
}
While this may work there's a note in the iOS Reference Library that troubles me:
Important: You must not modify the
fetch request. For example, you must
not change its predicate or the sort
orderings.
Source: NSFetchedResultsController Class Reference
This additional note doesn't exist in the iOS 3.2 Reference Library.
Just wanted to point this out.
An important note: if you "overwrite" a fetchController object make sure you clear its .delegate first - otherwise you'll get crashes when deleting rows, etc as the old fetchController and its delegate get events.