avoid duplicate results on Core Data fetch - xcode

I have a subclass of the CoreDataTableViewController (subclass of UITAbleViewController dome by the people on Stanford done to link CoreData and TableViews). On this Class, I want to perform a fecth, sorting by an attribute called "definition" and the code which executes it is the following:
- (void)setupFetchedResultsController{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:self.entity];
request.propertiesToFetch=[NSArray arrayWithObject:#"definition"];
request.returnsDistinctResults=YES;
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"%K != nil", #"definition"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"%K != ''", #"definition"];
NSPredicate *predicate3= [NSPredicate predicateWithFormat:#"%K contains[cd] %#", #"definition", self.seachBar.text];
NSArray *prepredicateArray;
if ([self.seachBar.text length]) {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2, predicate3,nil];
}else {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2,nil];
}
request.predicate=[NSCompoundPredicate andPredicateWithSubpredicates:prepredicateArray];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"definition" ascending:YES ]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
If I understood it correctly, setting request.returnsDistinctResults=YES; should avoid fetching duplicates. However it doesn't work and I'm seeing duplicates of this attribute's value.
Is there something I'm missing there? I'd appreciate some pointings there. Thank you in advance.
EDIT: If anyone is having the same issue here, after applying David's answer the resulting fetchedResultsController is just a NSDIctionary with object with only the requested value, which for displaying only purposes is quite fine. One thing I've done in cellForRowAtIndexPath in order to display the results on the cell label is:
Before:
HNMR *hnmr = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text=hnmr.definition;
After:
cell.textLabel.text=[[self.fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"definition"];

From the documentation of returnsDistinctResults:
This value is only used if a value has been set for propertiesToFetch.
From the documentation of propertiesToFetch:
This value is only used if resultType is set to NSDictionaryResultType.
From the documentation of resultType:
The default value is NSManagedObjectResultType.
This all tells me that the propertiesToFetch is ignored because you haven't set the resultType yourself and the default it to return managed objects instead of dictionaries. Since the propertiesToFetch is ignored the returnsDistinctResults is ignored as well and thus you are still getting duplicates.
Try setting the result type to return dictionaries instead of managed objects.
request.resultType = NSDictionaryResultType;

In addition to David Rönnqvist answer I suggest a useful link (with a sample) on selecting distinct values with Core Data:
core-data-how-to-do-a-select-distinct
Hope that helps.

Related

NSTreeController - Retrieving selected node

I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)

better way to find max date inside big pool of core data objects

i have big pool of core date objects (around 10000) and there is too long time doing code according profile:
NSDate *maxExternalChangedDate = [codes valueForKeyPath:#"#max.externalChangedDate"];
is community know better way to found it?
NSString *rateSheetID = [rateSheetAndPrefix valueForKey:#"rateSheetID"];
NSFetchRequest *requestCodesForSale = [[NSFetchRequest alloc] init];
[requestCodesForSale setEntity:[NSEntityDescription entityForName:#"CodesvsDestinationsList"
inManagedObjectContext:self.moc]];
[requestCodesForSale setPredicate:[NSPredicate predicateWithFormat:#"(%K.carrier.GUID == %#)",relationshipName,carrierGUID]];
NSError *error = nil;
NSArray *codes = [self.moc executeFetchRequest:requestCodesForSale error:&error];
if (error) NSLog(#"Failed to executeFetchRequest to data store: %# in function:%#", [error localizedDescription],NSStringFromSelector(_cmd));
NSNumber *count = [codes valueForKeyPath:#"#count.externalChangedDate"];
if (count == 0) {
[requestCodesForSale release];
[pool drain], pool = nil;
return YES;
}
NSDate *maxExternalChangedDate = [codes valueForKeyPath:#"#max.externalChangedDate"];
By using NSFetchRequest and returning NSDictionaryResultType You can use NSExpressionDescription to yeild the results for functions like max() and min().
Sample Code from Apple
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"salary"];
NSExpression *maxSalaryExpression = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"maxSalary"];
[expressionDescription setExpression:maxSalaryExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
Check out this doc for more information.
Core Data Programming Guide
I have the same issue.
In theory this should work, but for me it did not.
For some reasons the query crashes with the error that the database is corrupt.
In the end I perfomed a query, where I ordered on my field DESCENDING, and using setFetchLim it:1. Its not perfect, but at least it worked.
Also I made sure the field I use has an index.
Since I have few records, this works fine.
On 30000 records, it might be a problem though.
I followed the IOS documentation, fot fatch a "max:" query, but only got "database corrupted" errors. That is the sample code from Apple fails BADLY.
Googling the internet, it seem the call to setPropertiesToFetch fails in IOS 5+ ??!
I have not found any way around that.
Using a normal query, it worked without any issue.
So I must conclude Apple code is no longer corerct.

CoreData predicate latest stored date

I need to get the latest date from coredata
i found a way
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[fetchRequest setFetchLimit:1];
so sort them by date and then pick the first
however can this not be done more optimal? this approach looks like a brute force
sort is nlogn, but simple search for the max is n
You can actually ask SQL for just that value, not the object with that value:
NSExpression *date = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDate = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:maxDate]];
NSExpressionDescription *d = [[[NSExpressionDescription alloc] init] autorelease];
[d setName:#"maxDate"];
[d setExpression:maxSalaryExpression];
[d setExpressionResultType:NSDateAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:d]];
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
} else {
if (0 < [objects count]) {
NSLog(#"Maximum date: %#", [[objects objectAtIndex:0] valueForKey:#"maxDate"]);
}
}
This is described in more detail under Fetching Managed Objects -> Fetching Specific Values in the CoreData documentation.
On line 2 maxDate expression refers to itself (maxDate).
I assume this must be the "date" variable from the first line.
I guess that's pretty much the best way. I'm not sure whether there is a more efficient way since it has to compare every date anyway to figure out which the oldest is.
Here are 2 other ways:
1) You could work with BOOLs as an attribute of the managed object. (like oldest = 1)
However, you'd have to find a new "oldest" managed object every time you delete one.
2) You could just save the oldest one until it changes. This might save a lot of work if you have to find the oldest managedObject often.
It depends on your application (how many times you insert/remove managed objects and how many times you need the oldest object).

Core Data Transformable attributes NOT working with NSPredicate

I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];

NSArray to Core Data items

I have an method that reads an xml file and stores the xml nodes at a certain XPath-path in an NSArray called *nodes. What I want to do is take each one of the items in the array and add it to a core data entity called Category with the attribute of "name".
I have tried a number of different ways of creating the entity but I'm not sure about the correct way to do this effectively. This is the code used to create the NSArray, any ideas on how to implement this? (ignore the NSError, I will fix this in the final version)
- (IBAction)readCategories:(id)sender
{
NSString *xmlString = [resultView string];
NSData *xmlData = [xmlString dataUsingEncoding: NSASCIIStringEncoding];
NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithData:xmlData options:nil error:nil];
//XPath
NSError *err=nil;
NSArray *nodes = [xmlDoc nodesForXPath:#"//member[name='description']/value/string" error:&err];
}
EDIT - My loop code
NSArray *nodes = [xmlDoc nodesForXPath:#"//member[name='description']/value/string" error:&err];
int arrayCount = [nodes count];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSXMLElement *categoryEl;
NSString *new = [catArrayController newObject];
int i;
for (i = 0; i < arrayCount; i++)
{
[categoryEl = [nodes objectAtIndex:i]];
[new setValue:[categoryEl stringValue] forKey:#"name"];
[catArrayController addObject:new];
}
[pool release];
Here's how I'd write it:
for (NSXMLElement *categoryElement in nodes) {
NSManagedObject *newObject = [catArrayController newObject];
[newObject setValue:[categoryElement stringValue] forKey:#"name"];
[catArrayController addObject:newObject];
[newObject release];
}
First, I'm using the Objective-C 2.0 for-each syntax. This is simpler than using index variables. I eliminated i and arrayCount.
Next, I took out your NSAutoreleasePool. None of the objects in the loop are autoreleased, so it had no effect. (The newObject method returns a retained object which is, by convention, what methods with the word new in their name do) This is also why I release newObject after adding it to the array controller. Since I'm not going to be using it any more in this method, I need to release it.
Also, you had defined new (which I renamed newObject) as an NSString. Core Data objects are always either an instance of NSManagedObject or a subclass of NSManagedObject.
Your line [categoryEl = [nodes objectAtIndex:i]] won't compile. That's because the bracket syntax is used to send a message to an object. This is an assignment statement, so the bracket syntax is not needed here. (This line is also not necessary any more because of I've changed the loop to use the for-each syntax) But, for future reference, categoryEl = [nodes objectAtIndex:i]; would have worked.
What part are you having trouble with? There shouldn't be much more to it than looping through the array, creating a new managed object for each entry, and setting the correct attributes. You can create the managed object with NSEntityDescription's -insertNewObjectForEntityForName:inManagedObjectContext: method.

Resources