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.
Related
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 = %#"
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.
I have a core data model with two tables (meal and ingredients). I am trying to save ONE meal with MANY ingredients. I have the code below which loops through an array of ingredients. I'm trying to save it, but I cannot redeclare the "entity" below. How do I do it? I've tried releasing it, but that didn't work! Thanks for any help.
for (x=0;x<ingredients;x++) {
NSEntityDescription *entity = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:managedObjectContext];
entity.name = #"test";
}
(this method does work saving ONE record out of the loop.. so that's not the problem)
You don't insert entities into contexts. You insert managed objects into contexts.
You should have something like:
NSManagedObject *myMO;
for (x=0;x<ingredients;x++) {
myMo = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:managedObjectContext];
[myMO setValue:#"test" forKey:#"name"];
}
Of course, if have an NSManagedObject subclass you can just set the 'name' property directly.
The important thing is not confuse entities with instances of NSManagedObject or its subclasses. Entities are just descriptions of how objects relate to each other inside the object graph of the managed object context. The context uses the entity descriptions to figure out how all the actual instances fit relate to one another and how they are fetched and stored.
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.
i created a new project with a tableview already done by default with the add button which add dates. But the project is im not familiar with the nsmanagedobject thing. I want to add specific string to this not dates. thx for help!!
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
It sounds like you need to learn about Core Data. I would also recommend Core Data: Apple's API for Persisting Data on Mac OS X by Marcus S. Zarra.
To display a string, rather than a date, you need to modify the data model. The template created a data model with a sigle Event entity that has a data attribute.
You can remove or rename this entity and modify the attributes as desired. You want to add a string attribute.
Then, you will want to specify the name of your new string attribute as the key for sorting in the fetch. You will also want to use it to set the value of the text in the table view cell's text label. Finally, you will need to modify the insert new object method to set a default value for the new attribute.
Looks like you've started a project using Core-Data. If you don't know what Core-Data is or how to use it, you can find out more here:
http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreData/CoreData.pdf