Cannot save strings into CoreData - swift

I'm trying to save strings I have in my string array into the Core Data. My .xcdatamodel looks like this:
My saving function (a method of a class called "Memory"):
func save(from: [String])
{
for i in 0..<from.count
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let saved = NSEntityDescription.insertNewObject(forEntityName: "Person", into: context)
saved.setValue(from[i], forKey: "password")
do
{
try context.save()
print("SAVED")
}
catch
{
print("ERROR - COULDN'T SAVE ", to)
}
}
print("NEW ", to, ": ")
print(save)
}
Lastly, inside my ViewController:
Memory().save(from: codes)
However, what I get is this:
Thread 1: Fatal error: Unresolved error Error
Domain=NSCocoaErrorDomain Code=134140 "Persistent store migration
failed, missing mapping model."
UserInfo={sourceModel=() isEditable
1, entities {
Person = "() name Person,
managedObjectClassName NSManagedObject, renamingIdentifier Person,
isAbstract 0, superentity name (null), properties {\n password =
\"(), name password, isOptional
1, isTransient 0, entity

You have made changes to your data model but you failed/forgot to migrate the model. If you don't have anything valuable in your current persistence store (SQLite database?) then I suggest you throw it away and let Core Data create a new one using the new model.
Otherwise you might want to fetch the previous version of your model from your source repository if you have one and do a proper migration. Here is a SO question of interest and Apple's documentation on migration

Related

Updating Coredata using threads

I am trying to update an entity in Coredata using the background/thread. I am passing two variables in the below function, one is date and the other one is Account that has 'to one' relationship with the transaction attribute. I am getting an error when I execute the below code.
The goal is to perform updates to coredata without freezing the tableview and other controls/views.
ERROR - *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'account' between objects in different contexts
public class Transaction: NSManagedObject {
#NSManaged public var transDate: Date?
#NSManaged public var account: Account?
class func addTransaction(transDate : Date, transAccount : Account){
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.persistentContainer.performBackgroundTask({ (context) in
let entity = NSEntityDescription.entity(forEntityName: "Transaction", in: context)
let CD = Transaction(entity: entity!, insertInto: context)
CD.transDate = transDate //updated successfully
CD.account = transAccount//Gets an error while performing this line of code.
do {
try context.save()
}
catch {
print("error in saving Transaction data")
}
})
}
}
You can not use objects from different context together, the transaction object is created in your background context but the account object is from your main context.
The solution is to get the corresponding account object from the background context using the objectID which is always the same between contexts.
let account = context.existingObject(with: transAccount.objectID)) as? Account
CD.account = account
Note that account is optional here so you need to add some error handling in case it is nil, this should never happen but still it needs to be handled.

Inserting child records is slow in coredata

I have close to 7K items stored in a relation called Verse. I have another relation called Translation that needs to load 7K related items with a single call from a JSON file.
Here is my code:
let container = getContainer()
container.performBackgroundTask() { (context) in
autoreleasepool {
for row in translations{
let t = Translation(context: context)
t.text = (row["text"]! as? String)!
t.lang = (row["lang"]! as? String)!
t.contentType = "Verse"
t.verse = VerseDao.findById(row["verse_id"] as! Int16, context: context)
// this needs to make a call to the database to retrieve the approparite Verse instance.
}
}
do {
try context.save()
} catch {
fatalError("Failure to save context: \(error)")
}
context.reset()
}
Code for the findById method.
static func findById(_ id: Int16, context: NSManagedObjectContext) -> Verse{
let fetchRequest: NSFetchRequest<Verse>
fetchRequest = Verse.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "verseId == %#", id)
fetchRequest.includesPropertyValues = false
fetchRequest.fetchLimit = 1
do {
let results =
try context.fetch(fetchRequest)
return results[0]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
return Verse()
}
}
This works fine until I add the VerseDao.findById, which makes the whole process really slow because it has to make a request for each object to the Coredata database.
I did everything I could by limiting the number of fetched properties and using NSFetchedResultsController for data fetching but no luck.
I wonder if there's any way to insert child records in a more efficient way? Thanks.
Assuming your store type is persistent store type is sqlite (NSSQLiteStoreType):
The first thing you should check is whether you have an Core Data fetch index on the Verse objects verseId property. See this stack overflow answer for some introductory links on fetch indexes.
Without that, the fetch in your VerseDao.findById function may be scanning the whole database table every time.
To see if your index is working properly you may inspect the SQL queries generated by adding -com.apple.CoreData.SQLDebug 1 to the launch arguments in your Xcode scheme.
Other improvements:
Use NSManagedObjectContext.fetch or NSFetchRequest.execute (equivalent) instead of NSFetchedResultsController. The NSFetchedResultsController is typically used to bind results to a UI. In this case using it just adds overhead.
Don't set fetchRequest.propertiesToFetch, instead set fetchRequest.includesPropertyValues = false. This will avoid fetching the Verse object property values which you don't need to establish the relation to the Translation object.
Don't specify a sortDescriptor on the fetch request, this just complicates the query

How to deal with concurrency on core data

I've been wandering around google, stackoverflow and internet trying to understand how to work with core data and deal with the concurrency.
Consider that we have 2 tables, Events and Rooms.
An Event can have 1+ Rooms.
FunctionA - AddEvent
FunctionB - AddRoom
FunctionC - SearchRoom -> returns RoomEntity or nil
My problem, I keep getting these errors
Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x10a507160) for NSManagedObject (0x1092f00c0) with objectID '0xd000000000040000 <x-coredata://A34C65BD-F9F0-4CCC-A9FB-1B1F5E48C70E/Rooms/p1>' with oldVersion = 116 and newVersion = 124 and old object snapshot = {\n location = Lisboa;\n name = \"\\U00cdndico LX\";\n} and new cached row = {\n location = Lisboa;\n name = \"\\U00cdndico LX\";\n}"
Notice the information of the Rooms is equal
my approach is the following.
1- I call the webservice once ( it brings a json with data of 3 types of Events ) These 3 all have the same json structure and share the same managedObjectContext passed by parameter
2- I create a managedObject
var managedObjectContext: NSManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext = persistentContainer.viewContext
managedObjectContext.parent?.mergePolicy = NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType
3-
managedObjectContext.perform(
{
do
{
try self.deleteAllEventsFromDb()
FunctionA(eventList, managedObjectContext) -> save
FunctionA(eventList2, managedObjectContext) -> save
FunctionA(eventList3, managedObjectContext) -> save
self.DatabaseDispatchGroup.enter()
try managedObjectContext.save()
self.DatabaseDispatchGroup.leave()
completion(Result.Success(true))
}
catch let error as NSError
{
print("Could not save. \(error), \(error.userInfo)")
completion(Result.Success(false))
}
})
4- For each Event I execute the same FunctionA to create and save the data in database (managedObjectContext.insert(eventEntity)) . This will work over several tables but lets only consider Events and Rooms(FunctionB).
5- FunctionA contains functionB. Function B search for an existing Room(FunctionC->returns entity?) if it doesn't exists(nil), it creates the entity ( should I save here? )
6- If a Room exists, gets the entity and tries to update the data
Not sure if its making any difference but when I save I do these saves I do it between a dispatchGroup
DatabaseDispatchGroup.enter()
try managedObjectContext.save()
DatabaseDispatchGroup.leave()
I was using a static managedObjectContext which was used for all the database requests but now I decided to create a objectContext per function which accesses the database.
I do keep the same persistentContainer and the same DispatchGroup for all requests
private override init() {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
}
}
It seems to me that my problem is that I am Storing the changes in memory, and always doing updates over the initial data meaning that when I execute save() the context the data is not updated for the next operation?
How/when am I suppose to execute the save function?
Thank you
Once context is being saved, global notification is being posted: ContextDidSave notification.
When using multiple contexts (and not using parent-child approach) you should use this notification and either:
Re-fetch/refresh data in case you need to update view or perform some operation on new data set (using either fetch request or refreshObjects: API).
Merge changes to other contexts (remember about thread confinement! do that only on proper context queues). (merge doc)
There are many articles about it, check for instance this tutorial
and documentation

How to Save to a Custom Join Table Core Data (many-to-many) without unique predicate - ManagedObjectID (Swift)?

I will be super thankful for any help. How can I save instances to a join table without a unique identifier as a predicate? Can I use the managed object id to check if the item exists already?
I'm building an app with different exercise plans. Each plan holds many exercise, and an exercise can belong to many plans. I have structured my data model to include a custom join table so that I can query the completion status of an exercise from within one plan.
I'm sourcing my data from a json file and would like to save it to core data. I'm able to correctly save my CoreExercise, and CorePlan tables, however am having difficulty understanding how to save the instance of the object in the intermediate join table, since I'm unsure of what predicate to use.
I've written a class function to check if the instance exists, and to save it if it doesn't.
class CoreExercisePlan: NSManagedObject {
class func coreExercisesForExercisePlan(exerciseInfo: Exercise, planName: String, inManagedObjectContext context: NSManagedObjectContext) -> CoreExercisePlan? {
let request = NSFetchRequest(entityName: "CoreExercisePlan")
request.predicate = NSPredicate() // Search for ObjectID here? / How?
if let exercisePlan = (try? context.executeFetchRequest(request))?.first as? CoreExercisePlan {
print("we have this exercise plan already saved")
return exercisePlan
} else if let exercisePlan = NSEntityDescription.insertNewObjectForEntityForName("CoreExercisePlan", inManagedObjectContext: context) as? CoreExercisePlan {
exercisePlan.status = 0
exercisePlan.progress = 0
print("we are creating new object")
return exercisePlan
}
return nil
}
private func updateDatabaseWithExercisePlans(){
managedObjectContext?.performBlock {
// Array of exercises for each plan:
let coffeePlanExercises = self.coffeeExercises
let subwayPlanExercises = self.subwayExercises
for exercise in coffeePlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "coffee", inManagedObjectContext: self.managedObjectContext!)
}
for exercise in subwayPlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "subway", inManagedObjectContext: self.managedObjectContext!)
}
do {
try self.managedObjectContext?.save()
} catch let error {
print("printing error here: \(error)")
}
}
}
Is there a way to get the objectID of the instance in the join table, and use that as a predicate? Thanks!

delete past save entries of core data in swift

I have a problem where i cannot completely delete data stored in core data entities. i am storing the username and password in a login system.
i am aware of using the .deleteObject() method but to be honest im not sure if this is actually working as i do a print statement to check in console what is stored and it appears to have not been deleted
here is my code:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.managedObjectContext
//add user to core data
let user = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: context)
do{
try context.save()
}catch{
print("there was an error saving to core data")
}
do{
let request = NSFetchRequest(entityName: "User")
let results = try context.executeFetchRequest(request)
if results.count > 0{
for item in results as! [NSManagedObject]{
let username = item.valueForKey("username")
let password = item.valueForKey("password")
context.deleteObject(item)
print("aawawaw \(username), \(password)")
}
}
}catch{
print("there was an error")
}
You are printing the values that are stored in the let constants .
Try to implement in for loop
context.deleteObject(item)
let username = item.valueForKey("username")
let password = item.valueForKey("password")
print("aawawaw \(username), \(password)")
You did not really delete the objects from CD.
You should:
delete the object.
store the context
then perform a new fetch on the entity
print all instances of the entity
What you actually do is different:
store the context (with which changes whatever, but noting related to your question)
fetch the entity
delete object from the CD context
print the object that is still around although detached from CD.
as far as t. You are doing:
we can see, you don't save anything and threfore don't save the deleted state.