core-datacore-data-migration

Core Data modifications not saved in two of three tables (maybe because of a previous Core Data migration?)


I'm a bit lost because of a problem I never experienced before, however, I have a suspicion. I use Core Data for data storage and DB Browser for SQLite for inspecting the database running in the Simulator.

Here's the relevant function where all Core Data handling happens:

/**
 Creates a new ComposedFoodItem from the ComposedFoodItemViewModel.
 Creates the related FoodItem and the Ingredients.
 Creates all relationships.
 
 - Parameter composedFoodItemVM: The source view model.
 
 - Returns: A Core Data ComposedFoodItem; nil if there are no Ingredients.
 */
static func create(from composedFoodItemVM: ComposedFoodItemViewModel, generateTypicalAmounts: Bool) -> ComposedFoodItem? {
    debugPrint(AppDelegate.persistentContainer.persistentStoreDescriptions) // The location of the .sqlite file
    let moc = AppDelegate.viewContext
    
    // Create new ComposedFoodItem (1)
    let cdComposedFoodItem = ComposedFoodItem(context: moc)
    
    // No existing composed food item, therefore create a new UUID
    cdComposedFoodItem.id = UUID()
    
    // Fill data
    cdComposedFoodItem.amount = Int64(composedFoodItemVM.amount)
    cdComposedFoodItem.numberOfPortions = Int16(composedFoodItemVM.numberOfPortions)
    
    // Create the related FoodItem (2)
    let cdFoodItem = FoodItem.create(from: composedFoodItemVM, generateTypicalAmounts: generateTypicalAmounts)
    
    // Relate both (3)
    cdComposedFoodItem.foodItem = cdFoodItem
    
    // Add cdComposedFoodItem to composedFoodItemVM
    composedFoodItemVM.cdComposedFoodItem = cdComposedFoodItem
    
    // Save before adding Ingredients, otherwise this could lead to an NSInvalidArgumentException (4)
    try? moc.save()
    
    // Add new ingredients (5)
    if let cdIngredients = Ingredient.create(from: composedFoodItemVM) {
        cdComposedFoodItem.addToIngredients(NSSet(array: cdIngredients))
        
        // Save new composed food item
        try? moc.save()
        
        // Return the ComposedFoodItem
        return cdComposedFoodItem
    } else {
        // There are no ingredients, therefore we delete it again and return nil
        moc.delete(cdComposedFoodItem)
        try? moc.save()
        return nil
    }
}

What the function does:

  1. Creates a new entry in table ComposedFoodItem
  2. Creates another new entry in another table FoodItem
  3. Relates both entries
  4. Saves the modifications (and as of here I can see both new entries in the DB with all relations created correctly)
  5. Creates another 1..n entries in a third table Ingredient and links these to the entry created in step 1

All this works fine, I can see all relations and entries in the database.

Then I quit and restart the app. The entry created in step 2 is still there, but the entries created in steps 1 and 5 are gone, as well as the relationships (of course).

My suspicion: I recently implemented a Core Data migration from Data Model version 1 ("EasyFPU") to version 2 ("EasyFPU 2"). In this migration, I have two custom migration policies for exactly the two tables, which are not stored. The migration policies are pretty simple (and identical for both tables):

/**
 No Ingredient is created in the destination model, i.e., there will be no Ingredients
 */
override func createDestinationInstances(forSource sourceIngredient: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
    // Do nothing on purpose
    debugPrint("Not migrating Ingredient with ID: \((sourceIngredient as? Ingredient)?.id.uuidString ?? "unknown")")
}

And what I suspect is, that this migration policies are somehow called when restarting the app, but I have no idea why, because the migration has already happened before. If I set a breakpoint in the debugPrint line of the code snippet above, I actually never reach this breakpoint - as expected. Nevertheless are the two tables Ingredient and ComposedFoodItem empty after restart.

My AppDelegate Core Data persistentContainer variable looks like this:

lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "EasyFPU")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

I tried to replace "EasyFPU" with "EasyFPU 2", but this apparently is not the version, but the container name.

Any idea? Thanks in advance!


Solution

  • Issue resolved - it was a stupid leftover from some testing - the tables were actively deleted at app start.