Primary Key behaviour in Core Data iPhone - iphone

I am making an app that parses feeds from xml and stores them using Core Data.
The issue I am dealing with at the moment is duplicate entries. Every feed I am parsing contains a unique id, something that I get in my model as an int. Now what I need is to tell Core Data not to store that entity if another with the same id already exists.
As an example, let's say my model has the following properties:
Story.id (int) - primary key
Story.title (NSString)
Story.date (NSDate)
What is the best way to implement that?
My approach would be to keep a record (an array) of all the IDs available in the database and before inserting anything, check if it exists. That could work for the size of my app, but I get the feeling that's not the right approach.

I see two ways of doing this. It seems to me that the latter (your proposed method) is the better solution.
I changed id to primaryKey since I don't think it would be a good idea to use id as a variable or method name in Object-C since it's a keyword. I might work, I've never really tried. Also I assumed primaryKey to be an NSNumber, since that is how would be stored in Core Data.
Method One would execute a fetch request on the context each time:
for (id data in someSetOfDataToImport) {
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Story" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"primaryKey = %d", primaryKey]];
NSUInteger count = [context countForFetchRequest:request error:nil];
[request release];
if (count > 0)
continue;
// Insert data in Managed Object Context
}
Method Two does what you proposed, cache the keys in an array and check it instead of going to the source:
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Story" inManagedObjectContext:context]];
NSArray * allStories = [context countForFetchRequest:request error:nil];
[request release];
NSMutableArray * allPrimaryKeys = [[allStories valueForKeyPath:#"#distinctUnionOfObjects.primaryKey"] mutableCopy];
for (id data in someSetOfDataToImport) {
if ([allPrimaryKeys containsObject:data.primaryKey])
continue;
[allPrimaryKeys addObject:data.primaryKey];
// Insert data in Managed Object Context
}
[allPrimaryKeys release];

I would warn you against premature optimization. It's the root of all programming evil and a waste of time.
Frankly, on the iPhone, its almost impossible to get such a huge object graph that you start to notice fetches bogging things down. I can't imagine you're processing hundreds of unique xml feeds every second.
Unless your dealing with hundreds of thousands of primary keys at once, the predicate method will take a trivial amount of time and resources while minimizing complexity, maintenance and programming time. It's the simplest and quickest solution so use it to start and then optimize only if you later determine its a bottle neck.

Related

How to model my Core Data entity?

I want to store NoteObjects in Core Data. Normally, a NoteObject has a NSString *mainText and an NSMutableArray *arrayOfTags (an array of NSStrings). I want to now use Core Data, but arrays are a tricky matter with core data. Typically a NoteObject won't have more than 50 tags in its array. So how should I model this? I have two options:
Use a transformable property to store the array
Use a to-many relationship, which I've read is the more "legit" way to do it.
Which one should I use and why? And how would I implement a to-many relationship with my simple structure? I can't seem to wrap my fingers around that concept.
Use to-many relationship. Because it's way better and easier during fetch requests. See the screenshots below. Pay attention to the Relationship manager on the right side, set "To-Many Relationship" from your NoteObject to Tags. Ignore the Player entity.
Oh and pay attention to the "Delete Rule". You might want to delete all the tags associated with a given NoteObject. So set it to Cascade in that case.
NoteObject entity
Tag entity
--Edit:
To add multiple tags you need to first fetch your NoteObject - I assume there will be some sort of ID parameter which you'll use to distinguish NoteObjects. CoreData will automatically generate the add/remove methods for Tags. You'll need to use code similar to the one below:
- (void)addTags:(NSArray *)tags toNoteObjectWithID:(NSString *)noteID {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"NoteObject"];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"noteID == %#", noteID];
[fetchRequest setPredicate:pred];
NoteObject *noteObject = [[[self managedObjectContext] executeFetchRequest:fetchRequest error:nil] lastObject];
for (NSString *tag in tags) {
Tag *t = [NSEntityDescription insertNewObjectForEntityForName:#"Tag"
inManagedObjectContext:[self managedObjectContext]];
t.body = tag;
t.noteObject = noteObject;
[noteObject addTagsObject:t];
}
[self saveContext];
}
You could use a transformable property, but then you need to write the transformer.
If you use a toMany relationship, you have to create an additional entity for tags, which presumably has only one attribute - the string value, and a single relationship. Extrapolating a little, I would guess that you have a finite set of values tags can take on, and you might someday want all notes that have tag X - then you would be able to fetch the entity containing the string value for X and then use that to fetch the NSArray of objects that have X in the relationship (whatever you called it).
Arrays were only tricky in Core Data because they weren't supported prior to iOS 5, so you had to include some attribute (like creation date) by which they could be sorted. If you don't mind requiring iOS 5, you can use ordered relationships. Results are returned in an NSOrderedSet, which is a lot like (and can can be converted to) an array. Or, just re-think the problem -- is the order of the tags on a note important to the note or the user? Would it be okay if you just display the tags in alphabetical order? If so, even better -- just use a plain old (unordered) to-many relationship.

CoreData basics – to-many relationship array data

As I am fairly new to CoreData and coming from a MySQL-DB background, the CoreData Moddeling is kind of hard to understand at some point. I am sure you can help me out with this basic question.
CoreData model-descripton:
My database-model basically consists of two entities. The first one is called "Manager", the second one is called "Zipcodes". The "Manager" has 3 attributes, which are negligible at the moment. The important thing in my opinion is here the relationship called "zipcodes". The "Zipcodes"-Entity has an attribute called zip, which is a 16 int. It has a relationship as well, called "manager".
No I'll get to the point: Each manager has multiple zicodes in which he is responsible for all sales. The problem is now that I've setup an manager entity and want to link multiple ziplcodes to him. The zipcodes per manager are seperated in one comma seperated string. (12345,56789,...)
First of all I am creating an Manager Entity.
Manager *manager = [NSEntityDescription insertNewObjectForEntityForName:#"Manager" inManagedObjectContext:self.managedObjectContext];
The next step is seperating all zicodes to an array.
Manager *manager = [NSEntityDescription insertNewObjectForEntityForName:#"Manager" inManagedObjectContext:self.managedObjectContext];
NSArray *zipcodesArray = [[dict objectForKey:#"zipcodes"] componentsSeparatedByString:#","];
for (NSString *zip in zipcodesArray) {
???
}
So now that's the point where I am stuck. As later on I have to check the zipcodes via a searchBar they should be separated in the database. Do I now have to create a managedObjectModel for each zipcode? How do I connect all of them with the "one" manager entity? I'am sure there is a way to achieve that but I don't really know how.
Hopefully my question is understandable. If there's anything you would like to know, feel free to ask.
Thank you guys!
for (NSString *zip in zipcodesArray) {
NSManagedObject* zipcode = [NSEntityDescription insertNewObjectForEntityName:#"Zipcode"
inManagedObjectContext:self.managedObjectContext];
[zipcode setValue:zip forKey:#"zip"];
[zipcode setValue:manager forKey:#"manager"];
}
By establishing the relation from the zipcode to the manager on the last line, Core Data will automatically take care of inserting the zipcode into the relation from the manager back to the zipcodes.
Create a managed object instance of Zipcode. Set that object's zip attribute to the value of the string from your zipcodesArray. When you're done, save the managed object to your data store, check for errors, and repeat until you've walked all the way through your zip code array.

how to implement that in core data

in SQL we use the following clause
where studentName like '%a' and StudentID = 1
how to do that in objective c and core data using setpredicate for fetchrequest
I have only two inputs part of the name #"a" and the ID #"1"
best regards
Take a look at the Predicate Programming Guide.
I would also like to add the Core Data Programming Guide - Fetching Managed Objects.
it worked with contains[cd] but I want
to know what if I passed the string
empty and want to search by ID only
Normally, you don't use empty strings in a fetch predicate because core data will try to match the empty string. Instead, you should create different fetch request for different circumstances. Fetch request are lightweight objects than can be stored in arrays (or even the data model itself.)
If this case, you would test for an empty string and if true use a fetch predicate that only looks for the StudentID.
In general, try to avoid thinking of Core Data in SQl terms. It's natural but dangerous. Core Data is not SQL. Entities are not tables. Objects are not rows. Columns are not attributes. Core Data is an object graph management system that may or may not persist the object graph and may or may not use SQL far behind the scenes to do so. Trying to think of Core Data in SQL terms will cause you to completely misunderstand Core Data and result in much grief and wasted time.
I agree with TechZen, you should be thinking of core data in terms of objects, as opposed to being sql backed. CD uses SQL as an implementation detail, not necessarily as a defining feature.
This is an example from the developer library
Creating Fetch Request Templates Programmatically
basicly it looks something like this using predicated...
NSManagedObjectModel *model = <#Get a model#>;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey:#"Publication"];
[requestTemplate setEntity:publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
#"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
[model setFetchRequestTemplate:requestTemplate
forName:#"PublicationsForAuthorSinceDate"];
[requestTemplate release];
so by looking at that your probably after something like this
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
#"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
hope this helps I'm currently setting up core data in my project and have been reading through this stuff all morning.

Core data predicate with group and MAX()

I have an object in core data that has 2 fields: Name and Date. Each time a person checks in it saves their name and the time they did it.
Is there a way to get a list of all unique people with their last check in time using a predicate in core data?
Note: i do know that 2 tables would make it easier.
the sql for this would be
select
clientName
, MAX(ModifiedDate)
from
Client
group by
clientName
Beginning from iOS 5.0 / OS X 10.7 NSFetchRequest class includes the setPropertiesToGroupBy: instance method, which allow to create aggregated requests on persistent storage level.
You would have to set NSDictionaryResultType as the result type, thus telling NSFetchRequest object to return NSDictionary objects with aggregated data instead of managed objects.
Example code for max() requests:
[request setResultType:NSDictionaryResultType];
NSExpression *dateKeyExpression = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDateExpression = [NSExpression expressionForFunction:#"max:" arguments:[NSArray arrayWithObject:dateKeyExpression]];
NSExpressionDescription *maxDateED = [[NSExpressionDescription alloc] init];
[maxDateED setExpression:maxDateExpression];
[maxDateED setName:#"maxDate"];
[maxDateED setExpressionResultType:NSDateAttributeType];
NSAttributeDescription *clientName = [[self entityDescription].attributesByName objectForKey:#"clientName"];
[request setPropertiesToFetch:[NSArray arrayWithObjects:clientName, maxDateED, nil]];
[request setPropertiesToGroupBy:[NSArray arrayWithObject:clientName]];
No not easily since Core Data is an object graph first. You could do it by grabbing a distinct set of the names and then querying for the max for each via a sort.
The best answer is to break it into two tables otherwise it is going to be computationally expensive each time you try and resolve this.
OR/M Layer
Don't think of it as an OR/M layer. Core Data is an object graph; full stop.
The fact that it happens to persist to a database structure as one of its persistence options is secondary.
The issue in your question is that you are trying to use an Object Graph as a database. In that situation I get to use a phase that is very frequently attributed to me:
"You're doing it wrong." :)

Is Core Data more efficient than custom indexing for "as-you-type" searching?

Actually, I'm working on a Core Data based iPhone application. I have two entities which contain more than 200000 rows each and I'm experiencing some performance issues during retrieval of data. For each fetch request, I must wait between 1 and 2 seconds before getting results.
I'm thinking of implementing a custom search engine to index my data but the problem is that the whole database is editable. The contents can be changed at anytime so indexing a dynamic content database is stupid.
I'm wondering if Core Data is efficient enough to provide instant search. In the Apple documentation, a thousand rows entity is considered as small. Is that right?
Does anyone have a solution to improve the speed of Core Data...? Or should I implement my own search engine?
The goal is to provide an instant search, a search as you type mecanism.
[UPDATE]
Below is a snippet of one of my fetch request...
NSString *predicateString = [NSString stringWithFormat:#"^(.*\\b)?%#(\\b.*)?$", searchString];
NSString *predicate = [NSString stringWithString:#"text MATCHES[cd] %#"];
NSArray *arguments = [NSArray arrayWithObjects:predicateString, nil];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:[[HYDataManager instance] managedObjectContext]]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:predicate argumentArray:arguments]];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:#"length" ascending:YES], [[NSSortDescriptor alloc] initWithKey:#"subEntity.attr1" ascending:YES], [[NSSortDescriptor alloc] initWithKey:#"subEntity.attr2" ascending:YES], nil]];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"subEntity"]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HYDataManager instance] managedObjectContext] sectionNameKeyPath:nil acheName:nil];
You can try several things.
1) try using setFetchBatchSize: to reduce the working set of data in your app. In combination with an NSFetchedResultsController this will transparently faults batches on demand, showing the objects on your table as needed.
2) verify if you can use setResultType: with NSManagedObjectIDResultType. This will return only the ID of the matching objects; if you need to access just one or a few of them after performing the fetch request, then the additional overhead is really small, but the fetch is much faster. If you need to access all of the returned objects, then this is not the way to go.
3) if you instead need to retrieve the properties stored in your objects, use setPropertiesToFetch: as suggested by Hunter to retrieve only the ones you really need
4) if your model includes subentities of your entity, then verify if you can use setIncludesSubentities: passing NO as argument.
5) if you do not need to deal with all of the objects matching the predicate associated to your fetch request, then use setFetchLimit: to retrieve a fixed number of objects.
Hope this helps.
Have you tried marking all editable elements indexed in the data model? As you said it seems really odd to index everything but Core Data should be smart enough to handle this appropriately.