I am working with several NSManagedObject types with several relationships. How can I tell Core Data to automatically populate object IDs for me? I'm looking for something like an index key in SQL, so that no two instances of a given object are allowed to have the same ID.
Edit:
I'd like for all of my "Account" objects to have unique IDs on them. I was just adding one to the `countForFetchRequest, but I realized that when deleting the second to last object and then adding one, the last two objects now have the same IDs.
How can I ensure that a given value has a unique value for all instances of my "Account" NSManagedObject?
EDIT2:
I need to have a separate ID for sorting purposes.
All NSManagedObjects automatically have a unique NSManagedObjectID. There is no notion of a custom auto-incrementing attribute, but it's certainly easy to write one yourself.
The way I resolved this is with Core Data aggregates. I actually end up assigning the ID myself.
Essentially, I query Core Data for all of the entity IDs of my entity and then iterate through them. If I find an ID which is higher than the current temporary one, I make the temporary ID higher one higher than the aggregated one. When I'm done, I automatically have an ID which is higher than the highest one in the list. The only flaw I see with this is if there is a missing ID. (I believe that there is a simple fix for this as well.)
//
// Create a new entity description
//
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:self.managedObjectContext];
//
// Set the fetch request
//
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:entity];
//
// We need to figure out how many
// existing groups there are so that
// we can set the proper ID.
//
// To do so, we use an aggregated request.
//
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"entityID"]];
NSError *error = nil;
NSArray *existingIDs = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error != nil) {
//
// TODO: Handle error.
//
NSLog(#"Error: %#", [error localizedDescription]);
}
NSInteger newID = 0;
for (NSDictionary *dict in existingIDs) {
NSInteger IDToCompare = [[dict valueForKey:#"entityID"] integerValue];
if (IDToCompare >= newID) {
newID = IDToCompare + 1;
}
}
//
// Create the actual entity
//
MyEntity *newEntity = [[MyEntity alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
//
// Set the ID of the new entity
//
[newEntity setEntityID:[NSNumber numberWithInteger:newID]];
//
// ... More Code ...
//
Accroding to your EDIT2 and Edit3, following answer will help you.. Assume your id field as NSNumber having unsignedInt as ID.
1) Fetch all records for corresponding entity.
NSError *error = nil;
NSArray *array = [self fetchAllFileEntity:&error];
2) Find maximum number belonging to that result.
NSNumber *maxValue = nil;
if (array)
maxValue = [array valueForKeyPath:#"#max.uniqueId.unsignedIntegerValue"];
else
maxValue = [NSNumber numberWithUnsignedInteger:0];
3) Assign maxValue+1 to your new entity
entity.uniqueId = [NSNumber numberWithUnsignedInteger:maxValue.unsignedIntegerValue+1];
I have come up with this solution for the said problem, hope it's gonna be helpful for some one.
AppDelegate *appdelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appdelegate managedObjectContext];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *chatHist = [NSEntityDescription
entityForName:#"ChatHistory" inManagedObjectContext:context];
[fetchRequest setEntity:chatHist];
int chatIdNumber = 0;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if ([fetchedObjects count] > 0) {
ChatHistory *chatHistObj = [fetchedObjects objectAtIndex:[fetchedObjects count]-1];
chatIdNumber = [chatHistObj.chatId intValue];
}
chatIdNumber = chatIdNumber+1;
ChatHistory *chat_History = [NSEntityDescription insertNewObjectForEntityForName:#"ChatHistory" inManagedObjectContext:context];
chat_History.chatId = [NSString stringWithFormat:#"%d",chatIdNumber];
Related
I work on app that use Core data to save data in local device. In Core data i have save data in array using Transformable format but, i don't know how to update particular values in the array.
My code for update Array is here
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *user = [NSEntityDescription insertNewObjectForEntityForName:#"Type" inManagedObjectContext:context];
NSError *error = nil;
//Set up to get the thing you want to update
NSFetchRequest * request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Type"inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"businessTypes == %#", #"Others"];
[request setPredicate:predicate];
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (results == nil) {
// This implies an error has occurred.
NSLog(#"Error from Core Data: %#", error);
} else {
if (results.count == 0) {
// No objects saved, create a new one...
} else {
// At least one object saved. There should be only one
// so use the first...
user = [results lastObject];
[user setValue:#"Management" forKey:#"businessTypes"];
}
}
if (![self.managedObjectContext save:&error]) {
//Handle any error with the saving of the context
}
else{
[app saveContext];
NSLog(#"update value successfully");
}
and below is my save array in core data:
{
businessTypes = (
"Social Bussiness",
Marketing,
Transports,
Others
);
},
so i want to update "Others" to "Management" in the array.
When i run this code i have no error but i don't update particular value at index array.
thanks to help me.
Perhaps you are confusing your entities. You fetch an entity called Type but you are calling the object user, indicating that perhaps you wanted to fetch a user that has a certain business type.
If each user has only one "business type", you do not need a Type entity, just a string attribute for the User entity.
If each user can have more than one business type, you should have an entity Type with a name attribute that includes one term indicating the business type, and it should be modeled as a many-to-many relationship.
User <<--------->> Type
To set all types that are now called "Other" to "Management", you would fetch the Type with name "Other", change it and save. To only change one of a user's business types from "Other" to "Management", you would: fetch the user, remove the "Other" type, fetch the "Management" type, add it to the user and save.
If your businessTypes attribute is supposed to be a transformable array with hard-coded strings, you should probably change the data model as described above. You will have much more flexibility and power for searching and handling the data with a clean Core Data model.
You have to modify your update function like this code then you will get your required output
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error = nil;
NSFetchRequest * request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Type"inManagedObjectContext:context];
[request setEntity:entity];
request.propertiesToFetch= #[ #"businessTypes"];
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (results == nil) {
// This implies an error has occurred.
NSLog(#"Error from Core Data: %#", error);
} else {
if (results.count == 0) {
// No objects saved, create a new one...
} else {
int loopCount = (int)results.count;
Type* entityType = nil;
for (int index=0; index<loopCount; index++) {
entityType = (Type*)results[index];
if (entityType.businessTypes!=nil) {
NSUInteger reqIndex = [entityType.businessTypes indexOfObject:#"Others"];
[entityType.businessTypes replaceObjectAtIndex:reqIndex withObject:#"Management"];
[entityType setValue:entityType.businessTypes forKey:#"businessTypes"];
}
}
}
if (![self.managedObjectContext save:&error]) {
//Handle any error with the saving of the context
}
else{
[app saveContext];
NSLog(#"update value successfully");
}
I have two entities: Login (user id, password) and Information (title, info).
Now there is a 1 to 1 relation between them.
I need to add some information in the database unique to a user.
My code is below here:
Login *information = [NSEntityDescription insertNewObjectForEntityForName:#"Login"
inManagedObjectContext:self.managedObjectContext];
information.information.title = informationTitleTextView.text;
information.information.info_1 = information1textview.text;
information.information.info_2 = information2textview.text;
[self.managedObjectContext save:nil]; // write to database
[self.delegate savebuttontapped:self];
But It's not Working.I don't Know, What i am doing wrong?Any help would be appreciated.
You haven't added an instance of Information to the context. Try this:
Login *login = [NSEntityDescription insertNewObjectForEntityForName:#"Login" inManagedObjectContext:self.managedObjectContext];
Information *information = [NSEntityDescription insertNewObjectForEntityForName:#"Information" inManagedObjectContext:self.managedObjectContext];
login.information = information;
login.information.title = informationTitleTextView.text;
//...and so on...
Of course, if you're going to fetch a Login object based on its attributes, you'll want to actually store something in those attributes:
login.userId = theUserId;
login.password = thePassword;
At some point in the future, you'll probably want to fetch just the Login object that matches your criteria. Once you have that, you can get the associated information object without any trouble:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Login"
inManagedObjectContext:managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userId like %# AND password like %#", theUserId, thePassword];
[request setPredicate:predicate];
NSError *err = nil;
NSArray *matchingLogins = [self.managedObjectContext executeFetchRequest:request error:&err];
int count = [matchingLogins count];
if (count != 1) {
NSLog(#"Houston, we have a problem.");
}
Login *login = [matchingLogins objectAtIndex:0];
Information *info = login.information; // Notice: no separate fetch needed
How do I fetch all child entities of a parent?
I have a table populated by a parent entity in Core Data. When the user touches a cell I intend to show another table with all children of that parent.
How does the NSFetchRequest look like for this please?
Edit:
model is like this:
student>>dates [one to many, one student have many days]
So I want all dates for any given student (selected by touching in student table cell for that student), then populate dates table with dates for that student.
Thanks!
Assuming that the entity and the class names are Student and Date, and the reverse relationship for Date->Student is called student,
Student *aStudent = ...;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity: [NSEntityDescription entityForName: #"Date" inManagedObjectContext: [aStudent managedObjectContext]]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"student == %#", aStudent]];
You don't need a separate fetch request for this. All of the objects from the to-many relationship (don't call them child entities, that is misleading and incorrect) are available by accessing the relationship from the student object - something like student.dates. This gives you an NSSet, you can sort it and turn it to an array if you need to.
Within your first table delegate, when you touch a specific cell, I'll inject the specific parent property to the second table controller. For example:
SecondController secondController = ... // alloc-init
secondController.studentToGrab = ...
where SecondController declaration has a studentToGrab property like the following:
#property (nonatomic, retain) Student* studentToGrab; // use strong with ARC, if non-ARC remember to release it
and in definition synthesize it.
Then in your second controller, within viewDidLoad method you could do:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourNameEntityForDate" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"student == %#", studentToGrab];
[fetchRequest setPredicate:predicate];
// you can also use a sortdescriptors to order dates...
NSError *error = nil;
NSArray *resultArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error != nil) {
NSLog(#"Error: %#", [error localizedDescription]);
abort();
}
// use resultArray to populate something...
A remark when you deal with table you could also use NSFetchedResultController class. It has advantages when used for displaying data in tables.
If you have custom classes, you could traverse the generated relationship (return [student dates]). That will get you an unordered NSSet on iOS4, or, you can do it with a fetch request (note I use ARC so no releases/autoreleases here):
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Date"
inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:3];
[predicates addObject:[NSPredicate predicateWithFormat:#"student == %#", aStudent]];
// You might add other predicates
[fetchRequest setPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:predicates]];
// and if you want sorted results (why not, get the database to do it for you)
// sort by date to the top
NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"dateAdded" ascending:NO]];
}
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *sorted = [moc executeFetchRequest:fetchRequest error:&error];
if (error) {
// Handle the error, do something useful
}
return sorted;
I have two entities: products and bundles. Each one has its class. A product can be in multiple bundles.
Entities are defined like this:
PRODUCTS
name, string
number, integer 16
fromBundle = to-many relationship to product
BUNDLE
name, string
number, integer 16
product = to-many relationship to fromBundle
Products were assigned to bundle like this:
// suppose bundle 1 is composed of products 1, 2, 3 and 4.
NSArray *myProd = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:4],
nil];
int bundleNumber = 1;
NSString *bundleName = #"My Bundle";
Bundle *aBundle = nil;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
request.entity = [NSEntityDescription entityForName:#"Bundle" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat: #"(number == %d)", bundleNumber];
NSError *error = nil;
aBundle = [[context executeFetchRequest:request error:&error] lastObject];
// as the bundle does not exist, this will run
if (!error && !aBundle) {
aBundle = [NSEntityDescription insertNewObjectForEntityForName:#"Bundle" inManagedObjectContext:context];
aBundle.string = bundleName;
aBundle.Number = [NSNumber numberWithInt:bundleNumber];
for (NSNumber *umNum in myProd) {
// the product with number = aNum is retrieved... yes it is valid at this point
Product *oneProduct = [ProductWithNumber:umNum inManagedObjectContext:context];
NSMutableSet *mutableSet = [oneProduct mutableSetValueForKey:#"fromBundle"];
[mutableSet addObject:aBundle];
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// everything is fine at this point.
Now I wish to retrieve a list of all products that belong to a specific bundle...
To do that, I am using this method on Bundle class
+ (NSArray *)ProductsInBundle:(Bundle*)aBundle inManagedObjectContext:(NSManagedObjectContext *)context
{
NSArray *all = nil;
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
request.entity = [NSEntityDescription entityForName:#"Products" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"(fromBundle == %#)", aBundle];
NSError *error = nil;
all = [context executeFetchRequest:request error:&error]; // crashes here
return all;
}
it crashes on the assigned line on the last method with the message "to-many key not allowed here" when I try to do this
NSArray *allProductsInBundle = [Bundle ProductsInBundle:aBundle inManagedObjectContext:self.managedObjectContext];
aBundle is valid at this point.
I think your predicate is wrong. You don't have a bundle property, but a fromBundle property.
If it is really fromBundle, then your predicate should be:
equest.predicate = [NSPredicate predicateWithFormat:#"(fromBundle == %#)", aBundle];
EDIT:
If you are trying to do operations on to-many relationships then you'll need to use the aggregate functions for the predicate. I think for your case you'll want the IN operation.
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html#//apple_ref/doc/uid/TP40001795-215891
Why are you doing a fetch when you have a relationship? That is heavy and expensive. Just request the products for the bundle via
[aBundle valueForKey:#"product"];
The fetch is unnecessary and forces a disk hit when you probably don't need one. Core Data most likely has the product relationship cached.
Also, when you are assigning a product to a bundle you do not need to get a mutable set. Just set the bundle into the product via:
[product setValue:bundle forKey:#"fromBundle"];
Core Data will manage the other side of the relationship.
I'am currently migrating an iphone application using SQLite to CoreData.
I need to do an INSERT or REPLACE to add only the new content, is there a way to do this or do I have to fetch all the DB and look for existing objects and add the new ones ?
Thanks.
Remember, Core Data is an object hierarchy that happens to persist to a DB so you need to look at it like an object graph and not a database.
Therefore, yes you need to check to see if the object exists already using some defined unique ID and if it does not currently exist you need to create the new object vs. updating the existing object.
update
You don't need to fetch all of the objects, search the store using a NSFetchRequest and a NSPredicate to check for existence; if it exists update, if it doesn't create it.
Basically we order the inserted(fetched) data and the json data so at every index, they have equal values. We then retrieve the value at the respective index and update the managed object and in the case the counter is larger than the fetched results length, we insert a new managed object.
//Add JSON objects into entity
- (void) insertUpdate:(NSArray *)jsonArrayData {
//sort JSON Data
NSSortDescriptor *idDescriptor = [[NSSortDescriptor alloc] initWithKey:#"entityID" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = #[idDescriptor];
NSArray *sortedJSONArray = [jsonArrayData sortedArrayUsingDescriptors:sortDescriptors];
//get entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"entity" inManagedObjectContext:self.managedObjectContext];
// Get the ids from json in sorted order.
NSMutableArray *entityIDs = [[NSMutableArray alloc]init];
for (NSDictionary *jsonValues in jsonArrayData) {
id value = [jsonValues objectForKey:#"entityID"];
[entityIDs addObject:value];
}
//sort to make sure json and fetched data are both sorted in same manner
[entityIDs sortUsingSelector:#selector(compare:)];
// create the fetch request to get all Entities matching the IDs
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
NSString *filter = #"(%K IN %#)";
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: filter, #"entityID", entityIDs]];
// Make sure the results are sorted as well.
[fetchRequest setSortDescriptors:
#[ [[NSSortDescriptor alloc] initWithKey: idName ascending:YES] ]];
// Execute the fetch.
NSError *fetchError;
NSArray *entityMatchingNames = [self.elRehabCoreData.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
int i = 0;
//loop over JSONData
for (NSDictionary *keyedValues in sortedJSONArray) {
id value = [keyedValues objectForKey:#"entityID";
//Create NSManagedObject
NSManagedObject *managedObject = nil;
int updateInsert = 0;
if(entityMatchingNames.count > i ){
//update
managedObject = [entityMatchingNames objectAtIndex:i];
if ([[managedObject valueForKey:#"entityID"] isEqual:[keyedValues valueForKey:#"entityID"]]){
updateInsert = 1;
}
}else{
//insert
managedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
updateInsert = 1;
}
i++;
//set value if updateInsert is true
// The updateInsert flag is an extra security to make sure we only update when the value in JSON data is equal that of fetched data
if (updateInsert) {
[managedObject setValue:value forKey:attribute];
}
}
//save core data stack
[self saveContext];
}