I'm using Core Data to cache data, here is my code to insert object:
//data array count is 15
for (NSDictionary *dictionary in dataArray)
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CacheData" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title LIKE '%#'",dictionary[#"title"]];
[request setPredicate:predicate];
NSArray *fetchArray = [context executeFetchRequest:request error:NULL];
if ([fetchArray count] == 0)
{
CacheData *cacheData = [NSEntityDescription insertNewObjectForEntityForName:#"CacheData" inManagedObjectContext:context];
[cacheData setTitle:dictionary[#"title"]];
[cacheData setLink:dictionary[#"link"]];
[cacheData setPublishDate:dictionary[#"pubDate"]];
NSError *insertError = nil;
if (![context save:&insertError])
{
return NO;
}
}
}
The count of dataArray is 15, so I should insert 15 items to Core Data.
But once I used NSFetchRequest to fetch items, the array count returned added 1, and become 16, and fetch items again, it added 1 to 17 again:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CacheData" inManagedObjectContext:[[CacheDataManagement sharedInstance] managedObjectContext]];
[request setEntity:entity];
NSArray *fetchArray = [[[CacheDataManagement sharedInstance] managedObjectContext] executeFetchRequest:request error:NULL];
for (CacheData *data in fetchArray) {
NSLog(#"fetch:%#",[data title]);
}
NSLog(#"%ld",[fetchArray count]); //fetch array count is 16
Something wrong with my code ?
Update
Changed if ([fetchArray count] != 0) { … } to if ([fetchArray count] == 0) { … }
Since the problem it's not quite clear to me (I would like to have more details), I'll try to give you some hints.
First, the save should be done outside the for loop (In addition it's not correct to do a return within it).
// for in loop here
NSError *insertError = nil;
if (![context save:&insertError]) {
NSLog("%#", insertError);
return NO; // do it if the method you are running in returns a bool
}
Second, to check for duplicated you should rely on a GUID but if don't have it the predicate should look like the following.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title == %#",dictionary[#"title"]];
Third, also the execute for executeFetchRequest:error: should be replace with countForFetchRequest:error: since you don't need to return the objects but only a count. According to Apple doc, it
Returns the number of objects a given fetch request would have
returned if it had been passed to executeFetchRequest:error:.
Finally, in the for loop you are executing a request each time. My suggestion is to move execute a request before the loop and then checking for results within it. This according to Implementing Find-or-Create Efficiently. In this case, the pattern will enforce you to have a GUID for you entity.
Obviously, these are just hints. The real way to find the problem is to debug. In addition, I will perform tests starting from a fresh environment, i.e. the app has been deleted from the simulator or device.
In your code [fetchArray count] != 0 checks the inserting data is already exists. Try changing it like
if ([fetchArray count] == 0)
{
CacheData *cacheData = [NSEntityDescription insertNewObjectForEntityForName:#"CacheData" inManagedObjectContext:context];
[cacheData setTitle:dictionary[#"title"]];
[cacheData setLink:dictionary[#"link"]];
[cacheData setPublishDate:dictionary[#"pubDate"]];
NSError *insertError = nil;
if (![context save:&insertError])
{
return NO;
}
}
Related
NSFetchRequest in the below code is fetching in multiples times count of the data.. Example I have 3 as array count 1st time the method is called, next time it become 6 next 9 and so on. But in the database, the count remains as 3. Why is this happening?
// the method is called in viewDidLoad
- (void) create {
[orderList removeAllObjects];
NSError * error;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"OrderList"
inManagedObjectContext:context];
for (int addOrd = 0; addOrd < orderMainArray.count; addOrd++) {
GetOrders *getOrd = (GetOrders*)[NSEntityDescription insertNewObjectForEntityForName:#"GetOrders" inManagedObjectContext:context];
OrderList *ordList = (OrderList*)[NSEntityDescription insertNewObjectForEntityForName:#"OrderList" inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"orderId contains[cd] %#", [[orderMainArray objectAtIndex:addOrd] objectForKey:#"orderId"]];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
NSLog(#"\n \n \n count for same data = %d", [context countForFetchRequest:fetchRequest error:&error]);
if ([context countForFetchRequest:fetchRequest error:&error] ==0 ) {
// all data is saved here
[getOrd addOrderListObject:ordList];
if ([context save:&error]) {
NSLog(#"The save was successful! %#", ordList.orderId);
}
else {
NSLog(#"The save wasn't successful: %#", [error userInfo]);
}
}
}
NSFetchRequest *fetchRequest1 = [[NSFetchRequest alloc] init];
[fetchRequest1 setEntity:entity];
orderList = [[context executeFetchRequest:fetchRequest1 error:&error] mutableCopy]; // array count increases every time the method is called
}
Each time I call insertNewObjectForEntityForName:InManagedObjectContext: I was creating and adding new objects. So changed that and now works fine.
The code mixes fetch requests and inserts of new managed objects.
You insert one OrderList NSManagedObject and one GetOrder NSManagedObject in each iteration of the for-loop. Given your description, I would assume that orderMainArray.count has a value of 3.
I have a method to create managed objects (IntersectionObject) each with three properties. These three properties are managed objects themselves.
PropertyObject1, PropertyObject2, and PropertyObject3 each has about 20 different possibilities.
An IntersectionObject is essentially a combination of a particular PropertyObject1, PropertyObject2, and PropertyObject3.
There are about 1200 IntersectionObjects and to create them I am using a fetch request to retrieve and set the the correct PropertyObject:
- (PropertyObject1 *)fetchedPropertyObject1FromID: (NSNumber *)propertyObjectID {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PropertyObject1" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"error fetching PropertyObject from ID, error:%#", error);
return nil;
}
for (PropertyObject1 *object in fetchedObjects) {
if (object.propertyObjectID.longValue == propertyObjectID.longValue) {
return object;
}
}
return nil;
}
I am finding that repeating this fetch three times for each of the 1200 IntersectionObjects takes about 2 seconds, and is too slow. Is there a faster way to do this?
EDIT: the accepted answer has the solution that I used in the comments below it. It turns out simply mapping the PropertyObjects to a dictionary was the simplest and quickest way to get the associated objects.
If you have the managed object id, use objectWithID: on the MOC.
If you don't, and you're going to be creating a lot of associations in a short space of time, fetch all of the managed objects from the MOC in batches (perhaps 100 items each batch, see fetchBatchSize on NSFetchRequest), create a dictionary mapping your objectID to the managed objects so you can just do a local lookup. Turn each object back into a fault as you process the batch with refreshObject:mergeChanges:.
Use a predicate to do this, also set the fetch limit to 1. This should get your result in a much more optimized fashion:
- (PropertyObject1 *)fetchedPropertyObject1FromID: (NSNumber *)objectID {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PropertyObject1" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"objectID == %#", objectID]];
[fetchRequest setFetchLimit:1];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"error fetching PropertyObject from ID, error:%#", error);
return nil;
}
if(fetchedObjects.count > 0)
{
[return fetchedObjects objectAtIndex:0];
}
return nil;
}
I have a very simple fetch request that i want to execute. One of my entities has an attribute called smartCollectionIds and it is of type transformable. I use this attribute to store an NSArray of simple strings. In my code i use an NSfetchedResultsController to populate a tableview. The predicate im using is as follows:
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
however this causes a crash, specifically at the pint when i perform a fetch. However, if i first use a fetch to load all my objects into an array, and then filter them out with the above predicate, the app does not crash, and i get my results as expected. So basically
THIS CODE BELOW DOES NOT WORK
-(void) tryTO
{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Tweetary" inManagedObjectContext: [[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse]];
NSPredicate *predicate;
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
NSSortDescriptor *secondarySortKey = [[[NSSortDescriptor alloc] initWithKey:#"created_at" ascending:FALSE] autorelease];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease] ;
[request setEntity:entity];
[request setPredicate:predicate];
[request setSortDescriptors:[NSArray arrayWithObjects:
secondarySortKey
,nil]];
[request setFetchLimit:30]; //30
NSError *error;
NSArray *results = [[[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse] executeFetchRequest:request error:&error];
if (error != nil)
{
NSLog(#"Results are %d",[results count]);
}else{
NSLog(#"findAllObjectsInContext error %#",error);
}
}
BUT THIS WORKS
NSArray *tweets = [Tweetary findAllObjectsInContext:[[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse]];
NSLog(#"Before filter count is %d",[tweets count]);
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
NSArray *bNames = [tweets filteredArrayUsingPredicate:predicate];
NSLog(#"FINAL Results %d",[bNames count]);
+ (NSArray *)findAllObjectsInContext:(NSManagedObjectContext *)context;
{
#synchronized(self){
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
//NSLog(#"findAllObjectsInContext error %#",error);
}
return results;
}
}
In a nutshell, i need my fetch predicate to work when using NSfetchedResultsController instead of first loading up my objects into an array, and then applying my filter predicate. Can anyone point me in the right direction/ figure out why the predicate works only after i load my unfiltered data set into an array?
A Core Data fetch request with a predicate is translated to a SQLite query and executed
on the SQLite level. A transformable array is stored as some blob in the SQLite database,
therefore treating it as array in a fetch request does not work.
If you fetch the elements first, the blob is transformed back to an array
when the property is accessed. Therefore filtering the array of fetched objects works as expected.
I don't think there is any workaround. You cannot filter on transformable properties
in a fetch request.
I have an SearchStringTooltip ManagedObject. With property #dynamic tooltipText; (NSString)
I need to dynamically add new tooltips in database, but I need only unique values (insensitive).
They could come in more than 100 per one time; and every time I check for unique..
It looks like:
if (newTooltips.count == 0)
return;
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"SearchStringTooltip"
inManagedObjectContext:self.moc];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:entity];
for (NSString *name in newTooltips) {
[request setPredicate:[NSPredicate predicateWithFormat:#"tooltipText = %#", name]]; //like = (=) + time *2(sometimes *3) ofcourse i know i need like.. Its insensitive
NSInteger count = [self.moc countForFetchRequest:request error:nil]; //But its is very expensive operation expensive
if (count > 0) {
continue;
}
DBSearchStringTooltip *tooltip = [NSEntityDescription insertNewObjectForEntityForName:#"SearchStringTooltip"
inManagedObjectContext:self.moc];
tooltip.tooltipText = name;
}
How can I do it more cheaply for memory? There can be > 10 000 tooltips for check unique... And I have to check them all.
Quick answer and I am sure more efficient. Use this code snippet instead of your loop:
[request setPredicate:[NSPredicate predicateWithFormat:#"tooltipText IN %#", newTooltips]];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"tooltipText"]];
NSArray* result = [self.moc executeFetchRequest:request error:nil];
NSArray* existingTooltipNames = [result valueForKey:#"tooltipText"];
NSMutableOrderedSet* itemsToAdd = [NSMutableOrderedSet orderedSetWithArray:newTooltips];
[itemsToAdd minusSet:[NSSet setWithArray:existingTooltipNames]];
for (NSString *name in itemsToAdd) {
DBSearchStringTooltip *tooltip = [NSEntityDescription insertNewObjectForEntityForName:#"SearchStringTooltip" inManagedObjectContext:self.moc];
tooltip.tooltipText = name;
}
You may try to fetch only distinct tooltipText via NSDictionaryResultType result type of the fetch request and setReturnsDistinctResults:YES. Then search the results in memory. This could be much faster then 10k fetches, as far as fetch always hits the disk.
You may find more info here.
My situation is simple: I have some records in my core data store. One of their attributes is a string called "localId". There's a point where I'd like to find the record with a particular localId value. The obvious way to do this is with an NSFetchRequest and an NSPredicate. However, when I set this up, the request returns zero records.
If, however, I use the fetch request without the predicate, returning all records, and just loop over them looking for the target localId value, I do find the record I'm looking for. In other words, the record is there, but the fetch request can't find it.
My other methods in which I use fetch requests and predicates are all working as expected. I don't know why this one is failing.
I want to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#", localId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
if ([results count]) [context deleteObject:[results objectAtIndex:0]];
else NSLog(#"could not find record with localID %#", localId);
[self saveContext];
}
But I end up having to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
for (WCAAssessmentResult *result in results) {
if ([result.localId isEqualToString:localId]) {
NSLog(#"result found, deleted");
[context deleteObject:result];
}
}
[self saveContext];
}
Any clues as to what could be going wrong?
edit
I've found that I can use the predicate I'm creating to get the results I expect after the fetch request has been executed. So, the following also works:
- (WCAChecklistItem *)checklistItemWithId:(NSNumber *)itemId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAChecklistItem" inManagedObjectContext:context]];
NSArray *foundRecords = [context executeFetchRequest:request error:nil];
foundRecords = [foundRecords filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"serverId == %#", itemId]];
if ([foundRecords count]) {
return [foundRecords objectAtIndex:0];
} else {
NSLog(#"can't find checklist item with id %#", itemId);
return nil;
}
}
UPDATE
I've come across someone else experiencing this very issue:
http://markmail.org/message/7zbcxlaqcgtttqo4
He hasn't found a solution either.
Blimey! I'm stumped.
Thanks!
If localId is a numerical value, then you should use an NSNumber object in the predicate formation instead of an NSString.
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#",
[NSNumber numberWithString:localId]]];
NSPredicate format strings automatically quote NSString objects.
Hate to say it but the most common cause of these types of problems is simple typos. Make sure that your attribute names and the predicate are the same. Make sure that your property names and the attributes names are the same. If a reference to a property works but a reference to attribute name doesn't there is probably a mismatch.
You could test for the latter by comparing the return of:
[result valueForKey:#"localID"]
... with the return of:
result.localID