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];
}
Related
I have Core Data setup in my app and need to fetch a bunch of items and then access the properties I choose of those fetched items. I am able to successfully fetch a bunch of results like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
This gives me an array with my results, great. Now from this how can I for example get the 'name' property from these results? In this specific circumstance I want to load an array with all of the fetched results 'name' strings.
If I read your question correctly, you are able to fetch your NSManagedObjects without difficulty, but would like to derive another NSArray with name properties on those managed objects?
Then you can use the valueForKeyPath on the NSArray (extending your original code):
NSArray *names = [result valueForKeyPath:#"name"];
You can use the key-value:
for (NSManagedObject *fetchedResult in result) {
NSLog(#"name = %#", [fetchedResult valueForKey:#"name"]);
}
or if you created your custom NSManagedObject:
for (EntityObject *fetchedResult in result) {
NSLog(#"name = %#", [fetchedResult name]);
}
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];
I have a core data entity called images that has just 2 fields:
imageName = NSString
timeStamp = NSNumber
I am trying to simulate a kind of stack LIFO (last in first out).
Inserting a new entry is easy but what about reading the last entry added to the entity?
All images are added with a timestamp, obtained by using
time_t unixTime = (time_t) [[NSDate date] timeIntervalSince1970];
an integer that is equal to the number of seconds since 1970
so, how do I retrieve the last inserted record of a core data (= the record that has the biggest timestamp number)???
thanks
Perform a fetch request, sorting the results by timeStamp.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:...];
// Results should be in descending order of timeStamp.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"timeStamp" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSArray *results = [managedObjectContext executeFetchRequest:request error:NULL];
Entity *latestEntity = [results objectAtIndex:0];
You might also want to restrict the number of results using NSFetchRequest's setFetchLimit:.
I have tried using the method that Chris Doble mentioned and found it to be very slow, especially if there are lot of records that would need to be pulled and checked against the timeStamp. If you want to speed things up, I am now setting an attribute called isMostRecent on my ManagedObject's that I may ever want to get the most recent from. When a new record is to be stored I just grab the most recent record that has this attribute set to YES and change it to NO then set the new record that is being stored to YES. The next time I need to grab to most recent record all I have to do is this...
+ (Photo*)latestPhotoForMOC:(NSManagedObjectContext*)context {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCoreDataEntityNamePhoto
inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isMostRecent == %#", [NSNumber numberWithBool:YES]];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"isMostRecent" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
Photo* photo = nil;
if (mutableFetchResults && mutableFetchResults.count > 0) {
photo = [mutableFetchResults objectAtIndex:0];
}
return photo;
}
I have found this to be much faster. Yes, it requires a little more on your part to ensure it is used properly and that you don't ever end up with more than one record marked as isMostRecent but for me this was the best option.
Hope this helps someone else too.
In Swift 4, declare:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = [Entity]()
func getLastRecord() {
let entityCount = (entity.count - 1)
let lastRecord = entity[entityCount] // This is the las attribute of your core data entity
print(lastRecord)
}
I have a fetchedResultsController that has returned all records for my entity "Account".
I would like to quickly search all Account records for the attribute "lastName" == value, and give me back the Account object, or at least the indexPath of the object in the fetchedResultsController. There should only be 1 object returned.
Other than iterate through every objectAtIndexPath, is there a better way to search the fetchController using NSPredicate?
mootymoots, just filter the fetched objects with another predicate...
NSPredicate *lastNameMatch = ...
NSArray *matchingFetchedObjects = [fetchedResultsController.fetchedObjects filteredArrayUsingPredicate:lastNameMatch];
That leaves your fetchedResultsController results un-altered, but gives you an array with a match for the last name predicate.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// set up fetch request
...
NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(lastName like '%#')", self.lastName]];
[fetchRequest setPredicate:requestPredicate];
...
// perform fetch
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// handle error...
}
...
Account *uniqueAccount = [[self.fetchedResultsController fetchedObjects] anyObject]; // assuming lastName attribute is unique
The Core Data Documentation states that:
The fetch request associated with the [fetched] property can have a sort ordering, and thus the fetched property may be ordered.
How do I specify the sort descriptors for the fetched property in Xcode's data model editor? I can't find a relevant field anywhere. I'm developing for the iPhone platform, if this makes any difference.
If this is not possible via the graphical model editor, how do I go about modifying the fetch request for the fetched property in code so that it has a sort descriptor?
You can actually grab the model fetched property and add the sort descriptors to it (again, in code). I did this in the standard method that XCode generates in your AppDelegate if you choose one of the templates with Core Data:
By the way. This sorts ALL fetched properties on ALL models in your data model. You could get fancy and adaptive with it, but it was the most succinct way to handle sorting the 7 separate models that each had fetched properties that needed to be sorted by name. Works well.
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
*/
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
// Find the fetched properties, and make them sorted...
for (NSEntityDescription *entity in [managedObjectModel entities]) {
for (NSPropertyDescription *property in [entity properties]) {
if ([property isKindOfClass:[NSFetchedPropertyDescription class]]) {
NSFetchedPropertyDescription *fetchedProperty = (NSFetchedPropertyDescription *)property;
NSFetchRequest *fetchRequest = [fetchedProperty fetchRequest];
// Only sort by name if the destination entity actually has a "name" field
if ([[[[fetchRequest entity] propertiesByName] allKeys] containsObject:#"name"]) {
NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
[sortByName release];
}
}
}
}
return managedObjectModel;
}
You don't specify them in the graphical editor (as far as I know).
You specify them in the code where you make the fetch.
NSFetchRequest* request = [[NSFetchRequest alloc] init];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"whatYouAreLookingFor"
inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
// here's where you specify the sort
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:YES];
NSArray* sortDescriptors = [[[NSArray alloc] initWithObjects: sortDescriptor, nil] autorelease];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:#"myCache"];
The modeling tool doesn't appear to have a way to set the sort descriptors on the fetch request.
It should be possible[1] to, after loading the model but before associating it with a persistent store coordinator, to find the fetched property descriptions for which you want to control the sort order, and replace their fetch requests with fetch requests that have sort descriptors set on them.
[1] In principle this should work. In practice, I have not done so or tested it.
Using Tim Shadel's great answer I added per-NSManagedObject subclass sorting...
...in Tier.m (which is a NSManagedObject subclass)...
+ (void)initialize
{
if(self == [Tier class])
{
NSFetchedPropertyDescription *displayLessonPropertyDescription = [[[Tier entityDescription] propertiesByName] objectForKey:#"displayLesson"];
NSFetchRequest *fetchRequest = [displayLessonPropertyDescription fetchRequest];
NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:#"displayOrder" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
[sortByName release];
}
}
For a single fetched property, Swift 4, Xcode 9.4:
// retrieve the fetched property's fetch request
let fetchedPropertyRequest = (modelName.entitiesByName["entityName"]!.propertiesByName["fetchedPropertyName"] as! NSFetchedPropertyDescription).fetchRequest
// set up the sort descriptors
let sortDescriptors = [NSSortDescriptor(key: "keyName", ascending: true)]
// add the sort descriptors to the fetch request
fetchedPropertyRequest!.sortDescriptors = sortDescriptors
Here's the same thing the loooonnnnnnggggggg way:
// retrieve the fetched property's fetch request
let theEntityDescription: NSEntityDescription = modelName.entitiesByName["entityName"]!
let theFetchedPropertyDescription = theEntityDescription.propertiesByName["fetchedPropertyName"]! as! NSFetchedPropertyDescription
let theFetchedPropertyRequest = theFetchedPropertyDescription.fetchRequest
// set up the sort descriptors
let sortDescriptor1 = NSSortDescriptor(key: "keyName", ascending: true)
let theSortDescriptors = [sortDescriptor1]
// add the sort descriptors to the fetch request
theFetchedPropertyRequest!.sortDescriptors = theSortDescriptors
Note: for this example, I force-unwrapped values. Make sure that you account for optional values in your actual code!
Sadly, though, the ability to sort is somewhat limited. For example, you cannot take a field that is an NSString containing a number, and sort it numerically, at least not with a SQLite backing store. As long as you are sorting alphabetically on strings, numerically only on values stored as numbers and so forth, though, the NSSortDescriptor applied to the fetch request works just fine.
Put this into your NSManagedObject subclass:
+ (void)initialize
{
if (self != [EntityManagedObjectSubClass class]) return;
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSEntityDescription *entityDescription = [managedObjectModel entitiesByName][#"entityName"];
NSFetchedPropertyDescription *fetchedPropertyDescription = [entityDescription propertiesByName][#"fetchedPropertyName"];
NSFetchRequest *fetchRequest = [fetchedPropertyDescription fetchRequest];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"sortDescriptorKey" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
}
Replace EntityManagedObjectSubClass, entityName, fetchedPropertyName and sortDescriptorKey with your own stuff.
Jeff, if the strings are right-aligned, you could just sort on the strings; " 123" > " 23" and so on. But iirc ascii space is after the numbers, and if so, then what you would do is create a dynamic property that is an NSNumber (which supports the compare: method), and use the numberFromString: method to make a number from the string. Then you can specify the number field in the sort. In the interface:
#property NSString *stringIsaNumber; // in the data model
#property NSNumber *number;
in the implementation:
#dynamic stringIsaNumber;
- (NSNumber *) number ;
{ return [self.stringIsaNumber numberFromString]; }
- (void) setNumber:(NSNumber *)value;
{ self.stringIsaNumber = [NSString stringWithFormat:#"%5i",value) }
ps plz forgive coding errors, this is off the top of my head.