Issue with setting NSSortDescriptor key in NSFetchResultController - iphone

With respect to the above relationship diagram, Earlier, I had a too many relationship between Assignment and Question entity and this is broken to fix a question ordering issue.
Now the relationship to Assignment to Question is through a join table AssignmentQuestion.
Idea here was to sort the question based on the questionOrderIndex property of the AssignmentQuestion table.
I am fetching the assignment questions through a NSFetchResultController. I have written a predicate to fetch the questions accordingly. But since I want the questions to be sorted based questionOrderIndex, my sort key for the sort descriptor through question entity is assignmnetQuestion.questionOderIndex.
But with this sort key, I am getting an exception: "Exception = to-many key not allowed here". Upon investigation, I found that it is because of "one to many" relation from Question to AssignmentQuestion table.
My questions are:
1) How to set the sort key for a Sort descriptor based on questionOrderIndex?
2) Do we really need to have a join table here in core data to sort the questions in order?
Predicate to fetch Questions:
NSString *predicateString = [NSString stringWithFormat:#"self.evaluationQuestion.assignmentEvaluation.assignmentId == %#", #"1"];

If you don't need to listen for changes on the questions (external to your view controller), you could fetch the the AssignmentQuestion entities instead, and prefetch the attached questions.
later, in your view controller, instead of using the AssignmentQuestion entities, use their prefetched question relationship
//code not tested
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:#"AssignmentQuestion"];
[request setPredicate:[NSPredicate predicateWithFormat:#"assignmentEvaluation.assignmentId == %#",assignmentId]];
[request setRelationshipKeyPathsForPrefetching:#[#"question"]];
[request setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"questionOrderIndex" ascending:YES]]];
also see here

Related

Design for a chat app using Core Data

I'm writing a chat app and I'm in the process of changing my db to use Core Data. I currently use sqlite directly but I want to take advantage of iCloud feature so I'm switching the engine.
My main table is called Entry with the following properties:
NSInteger type;
NSDate* timestamp;
NSString* username;
NSString* session;
NSString* body;
where 'type' can be:
1 - message
2 - file transfer (which then 'body' represents a file name in the documents folder)
3 - user joined
4 - user left
My app also supports multi-user chat (hence why the 'user joined'/'user left' types). All messages belong to the same conversation (multi-chat only), will have a valid 'session' property.
In my chat history, my problem is how to achieve the 'load more' like Apple did in the SMS app: I will query based on 'username=%# AND session IS NULL' or 'session=%#' to show that history and use a LIMIT of 50 sorted by reversed 'timestamp'.
I then want to have a button "Load more" which will load the next 50 messages - I'm not sure how to do it with Core Data.
My next question is how to show the list of conversations. Right now with raw sqlite, I perform a join on 2 queries: the first is the last message of each user and the second is the last message of each multi-user conversation. I then sort them all by date.
Since Core Data does not support joins, I'm not sure how to perform this query.
Thanks
Having an app that does exactly the same thing, here are my insights.
First of all you should consider coredata and multithreading wisely before coding. If you need help on that let me know.
The model
You are working with entities in Coredata, which can be considered like tables in sqlite, but in a more abstract way. You should review Apple's documentation for that.
We can find at least three different entities in your case : User, Conversation, and Message. (be careful with the last one, I had an issue with the entity called Message when importing the SMS Framework, you should consider prefixing the name of the entity..)
An issue with coredata is that you can not store directly arrays (may be with some unknown type) but anyway. So two solutions to store your users : either in a NSString when they will be delimited by comas and a simple regex or split will give you the number of users..
so your model could look like :
Conversation{
messages<-->>Message.conversation
lastMessage<-->Message.whateverName
//optional
users<<-->>User.conversation
}
Message{
conversation<<-->Conversation.messages
whatevername<-->Conversation.lastmessage // "whatever as it does not really matter"
}
User{
conversations<<-->>Conversation.users
}
Conversation must have an to-many relationship to Message and Message a to-one relationship to Conversation.
--EDIT
If you want to display the last message of a conversation just like the message App (or my app), you can add one relationship with message. It won't store the message twice in the database/coredata. Indeed, you create a coredata object (in this case a message) and that you add it to a conversation, what happen inside is that a conversation store the coredata ID for that object. Adding one relationship for this message (lastMessage) will only store another ID, and not another object.
--end of EDIT
Users are slightly different because they can be part of multiple conversations (because of the group conversation) that is why you need a many-to-many relation ship.
You can add as many attributes as you want, but that's the minimal requirement !
Implementation
then in your code, if you want to mimic the behavior of iMessage, here is what I did :
in the first controller, where you can see all the conversation : use a NSFetchedResultController. The query should be only about the entity Conversation.
When clicking on a row, what I did is that the new view has the conversation object and another NSFtechedResultController. I then query only the entity Message but with a predicate specifying that I only want this conversation.
If you want to check my app to see the fluidity, go to this link.
EDIT
Code snippet to find the last Message Of A Conversation
Beware: This is a temporary answer before finding a better way to do it (i.e. when using fetched properties)
NSFetchRequest * req = [[NSFetchRequest alloc] init];
[req setEntity:[NSEntityDescription entityForName:#"Message" inManagedObjectContext:context]];
[req setPredicate:[NSPredicate predicateWithFormat:#"conversation == %#", self]]; /* did that from a Conversation object.. */
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"sent_date" ascending:NO];
[req setSortDescriptors:[NSArray arrayWithObject:sort]];
[sort release];
NSError * error = nil;
NSArray * messages = [context executeFetchRequest:req error:&error];
[req release];
if ([messages count] > 0) { /* sanity check */
return [messages objectAtIndex:0];
}
return nil;
--end of EDIT
Hope this help !
Pierre
First, your mental model is all wrong. You should not think of core data as a SQL database. Yes, most of the time it uses SQL, but it is merely an implementation detail. You should think in terms of object graphs.
Next, for your "50 items" issue, look at NSFetchRequest. You can tell it where to start (fetchOffset), and how many items to fetch (fetchLimit). There are other options for you as well. If your total number of items is relatively small, you can just fetch the entire array (and only fault so many at a time - see fetchBatchSize).
For your "join" consider how objects are related to each other, not database table joins. Unfortunately, I do not understand what you are trying to achieve with that part of the question. However, you can mimic "joined" tables by using the dot notation when forming your predicate.
EDIT
When you create a conversation object, you can include a to-many relationship to something like "participants" which would be a set of all the users that participated in that conversation. The inverse would also be a to-many relationship in "user" that contained all the conversations that user participated in (I assume your database has multiple users???).
So, to get all the conversations in which a particular user participated, you could do something like fetch on "Participant" with a predicate similar to "ALL participants.username = %#"

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.

iPhone: CoreData join

My question similar to question iPhone CoreData join. The difference is that I need to get all LanguageSets from a database for a given category.categoryName. How predicate will looks like ? Thanks...
I assume its the same schema an you are fetching LanguageEntry. You can assign a predicate to the fetch request like
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"category.categoryName==%#",<given name>];
Since you want only the LanguageSet , you should specify so in the fetch request
[request setPropertiesToFetch:[NSArray arrayWithObject:#"languageSet"]];
Don't forget to set resultType as NSDictionaryResultType.

how to obtain an ordered list of coredata managed objects via accessing them via a relationship?

How to obtain an ordered list of coredata managed objects via accessing them via a relationship?
That is:
have the following entities: LIST, LIST_ITEM (includes an 'Order' field), and ITEM.
assume that I have already fetched the list I want to work with
I can then use the coredata relationships to get the LIST_ITEMS via using the relationship: e.g. "list1.listItems", and then for each of these LIST_ITEMS I can get the ITEM ("listItem1.item")
But if I really just want, from the LIST, an ordered list of ITEMS from the list, based on the "Order" field in the LIST_ITEM, what is the easiest way of doing this?
You can sort the items returned by the relationship using an NSSortDescriptor just as you would in a regular fetch request. For example:
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"order" ascending:YES] autorelease];
NSArray *sortedListItems = [list1.listItems sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
The key method here is [NSSet sortedArrayUsingDescriptors:]
I think that I understand the question correctly, but I think the best way to get lists from CoreData is create a compoun predicate and then search for items that way. For example if I am looking for only Events (entity) on a certain day, for a specific User (another entity). Then I can create an NSFetchRequest for the Event entry and specify and NSPredicate in the form of (user.name==%#) AND (event.date==%#) specifying the user's name and date

Unique Values from Core Data

I have a core data-based app that manages records of auto dealerships. Each record stores the dealer's address, which is broken into addressLine1, addressLine2, city, state, and zip components, each stored as a string in the data store.
I would like to present a list of cities with dealerships to the user, so I'm trying to figure out if it is possible to get a list of every unique city name that has been entered into the store. I other words, is it possible to issue some sort of query against all of the dealership records that will return a list of distinct city names?
I would know how to do this easily with a SQL query, but (how) is this done in Core Data?
Thanks very much!
Core Data have the option to get distinct record. The method of getting unique results using NSArray and NSSets are not recommend.
[fetchRequest setResultType:NSDictionaryResultType];
NSDictionary *entityProperties = [entity propertiesByName];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:[entityProperties objectForKey:#"<<yourattrib>"]]];
[fetchRequest setReturnsDistinctResults:YES];
Refer Apple documentation and check the answers for How to fetch distinct values in Core Data?
You're right, there isn't an "easy" way to do this with Core Data, because Core Data is not a database. However, it is possible. Here's the general idea:
Fetch all your Dealer objects via an NSFetchRequest. To simplify the query, you can set the fetch request to only fetch the city attribute.
Execute NSArray * uniqueCities = [fetchedDealers valueForKeyPath:#"#distinctUnionOfObjects.city"];
A quick way to ensure a unique set of things is to use NSSet. Once you have the results for a query on city take your NSArray and do
NSSet* uniqueResults = [NSSet setWithArray:resultsArray];
You can transform the set into another collection class if more convenient or just the object enumerator to do something with all of them. I do not know if this approach or the valueForKeyPath method from Dave DeLong is more efficient.