删除相互依赖的对象时了解核心数据

时间:2016-11-04 05:29:56

标签: swift core-data swift3 ios10 nsfetchrequest

这个问题要求在以下场景中采用最佳做法:

附件是显示我的工作订单和服务核心数据实体的图像。请注意,删除规则当前不是工作订单的操作。 (注意改为Nullify不会解决我的问题,只会引起同样的问题)。另请注意,在服务上我对id有约束。这不允许重复。因此,我在下面提出了合并政策:

context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

合并策略将获取我发送的新数据并覆盖数据库中的内容作为默认值。如果没有这个,我的程序会因为它的编写方式而引发错误。

如果我使用这些设置运行我的代码,并且我对工作人员进行批量删除但不提供服务(因为我想保留这些),当我重新启动程序时,当我尝试添加*时会发生崩溃*对具有相同ID的服务的引用。

我的问题是为什么它会崩溃以及解决这个问题的最佳方法是什么?我目前的理论是这些实体可能有另一个唯一标识符,因为我删除了工作订单,它的引用是到不同的上下文版本的服务......当我使用与旧服务相同的id创建新服务时,它可能会假定相同的内部ID。我不确定这是否正在发生或如何确认。

我的代码发生在我的一个控制器的viewDidLoad方法中,看起来像这样。

override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()

        let context = gm_getContext()

        //Create default fetch request to get all workorders
        let fetchRequest: NSFetchRequest<Workorders> = Workorders.fetchRequest()

        do{
            //Run fetch request to get search results.
            let searchResults = try context.fetch(fetchRequest)

            //If no results were found and demo mode = true, lets create some default records.
            if(searchResults.count<=0 && g_demoMode==true){
                print("create default data")

                //Uncomment the following lines if you want to prove that the Merge Policy
                //Is working for Unique Constraints.
                let serviceFetchRequest: NSFetchRequest<Service> = Service.fetchRequest()
                let serviceSearchResults = try context.fetch(serviceFetchRequest)
                print("Services Count = \(serviceSearchResults.count)")

                //First we have to create a sample service
                let entity =  NSEntityDescription.entity(forEntityName: "Service", in: context)
                let service = NSManagedObject(entity: entity!, insertInto: context)

                service.setValue(1, forKey: "id")
                service.setValue("Tire Repair Service Sample", forKey: "name")
                service.setValue("<html>Test Service Field</html>",forKey:"templatedata")

                //add reference to the global
                g_services.append(service as! Service)

                //Proof that service is indeed a Service object and stored in global
                print("g_services[0].name = "+g_services[0].name!)

                //Save the service object (overwriting an old one with same id if needed)
                do {
                    try context.save()
                    print("Saved context with service")
                } catch let error as NSError  {
                    print("Could not save \(error), \(error.userInfo)")
                } catch {
                    print("Could not save, unknown error")
                }

                //Now create 3 sample work orders all using the same service template.
                let workorderEntity1 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
                let workorder1 = NSManagedObject(entity: workorderEntity1!, insertInto: context)

                print("created work order variable 1")

                workorder1.setValue(1, forKey: "id")
                workorder1.setValue("11402 Kensington Rd, Los Alamitos, CA, 90720", forKey: "address")
                workorder1.setValue("33.797472", forKey: "lat")
                workorder1.setValue("-118.084136", forKey: "lng")
                workorder1.setValue(15,forKey: "client_id")
                workorder1.setValue("Need to fix their tire fast", forKey: "desc")
                workorder1.setValue("(562)810-4384", forKey: "phone")
                workorder1.setValue(g_services[0], forKey: "service")

                print("Created first work order")

                let workorderEntity2 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
                let workorder2 = NSManagedObject(entity: workorderEntity2!, insertInto: context)

                workorder2.setValue(2, forKey: "id")
                workorder2.setValue("17078 Greenleaf Street, Fountain Valley, CA, 92708", forKey: "address")
                workorder2.setValue("33.714992", forKey: "lat")
                workorder2.setValue("-117.958874", forKey: "lng")
                workorder2.setValue(16,forKey: "client_id")
                workorder2.setValue("This guy does not know what he wants", forKey: "desc")
                workorder2.setValue("(562)777-3344", forKey: "phone")
                workorder2.setValue(g_services[0], forKey: "service")

                let workorderEntity3 = NSEntityDescription.entity(forEntityName: "Workorders", in: context)
                let workorder3 = NSManagedObject(entity: workorderEntity3!, insertInto: context)

                workorder3.setValue(3, forKey: "id")
                workorder3.setValue("17045 South Pacific Avenue", forKey: "address")
                workorder3.setValue("33.713565", forKey: "lat")
                workorder3.setValue("-118.067535", forKey: "lng")
                workorder3.setValue(17,forKey: "client_id")
                workorder3.setValue("Tire damaged by the beach", forKey: "desc")
                workorder3.setValue("(714)234-5678", forKey: "phone")
                workorder3.setValue(g_services[0], forKey: "service")

                //Don't need signature, pictures and videos because they just don't exist yet.

                //add reference to the global
                g_workOrders.append(workorder1 as! Workorders)
                g_workOrders.append(workorder2 as! Workorders)
                g_workOrders.append(workorder3 as! Workorders)

                print("Preparing to save to context for work orders")

                //Save the work order objects (overwriting any old ones with same id if needed)
                do {
                    try context.save()
                    print("Saved context with workorders")
                } catch let error as NSError  {
                    print("Could not save \(error), \(error.userInfo)")
                } catch {
                    print("Could not save, unknown error")
                }

            }else{
                print("WorkOrders Count = \(searchResults.count)")

                let workorderFetchRequest   = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
                //let workorderFetchRequest   = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
                let deleteWorkOrderRequest  = NSBatchDeleteRequest(fetchRequest: workorderFetchRequest) //Deletes ALL workorders

                //Perform Actual Deletion On Database Tables
                do{
                    try context.persistentStoreCoordinator!.execute(deleteWorkOrderRequest, with: context)
                }catch{
                    fatalError("Bad Things Happened \(error)")
                }

                print("deleted workorders")
            }

        } catch {
            print("Error with request: \(error)")

        }

        print("service table view controller loaded")
    }

我跟踪核心数据值的上下文和全局变量是在像这样的globals.swift文件中全局定义的。

 var g_workOrders = [Workorders]()
 var g_services = [Service]()

//Shortcut method to get the viewcontext easily from anywhere.
func gm_getContext () -> NSManagedObjectContext {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext

    //For unique constraints it will overwrite the data.
    context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

    return context
}

核心数据模型参考: enter image description here

enter image description here

其他笔记&amp;我试过的事情:

我知道它崩溃了这一行(workorder1.setValue(g_services[0], forKey: "service")),这就是我知道它与服务相关的方式,并且将规则更改为级联删除以修复崩溃,但它删除了附加到它的服务!...这是有道理但不是我想要的。

1 个答案:

答案 0 :(得分:0)

我最近找到了问题的答案,问题与多方面有关。

首先我的核心数据堆栈设置不正确。我现在已将其更改为此(由友好的开发人员朋友提供帮助)。

import UIKit
import CoreData

class DataController: NSObject {

    var managedObjectContext: NSManagedObjectContext
    static var dataController: DataController!

    override init() {
        // This resource is the same name as your xcdatamodeld contained in your project.
        guard let modelURL = Bundle.main.url(forResource: "WorkOrders", withExtension: "momd") else {
            fatalError("Error loading model from bundle")
        }

        // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
        guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
            fatalError("Error initializing mom from: \(modelURL)")
        }

        let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)

        managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = psc

        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let docURL = urls[urls.endIndex-1]
        /* The directory the application uses to store the Core Data store file.
         This code uses a file named "DataModel.sqlite" in the application's documents directory.
         */
        let storeURL = docURL.appendingPathComponent("WorkOrders.sqlite")
        do {
            let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
            try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
        } catch {
            fatalError("Error migrating store: \(error)")
        }

    }

    class func sharedInstance() -> DataController {

        if (dataController != nil) {
            return dataController
        }

        dataController = DataController()

        return dataController
    }
}

每当我需要访问coreData时,我现在应该这样做......

let context = DataController.sharedInstance().managedObjectContext

需要注意的另一件事是Datacontroller中的并发设置设置为主线程。这也是问题的一部分,因为我在一个线程中运行我的代码。

它设置为DataController

中此行的主线程
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

因此,每当您要访问或保存数据到coreData时,总是将其包装在主线程的调用中,如下所示......

DispatchQueue.main.async {
   AppDelegate.appDelegate.saveContext()
}

最后,我遇到的最后一个问题是我正在使用以下命令进行批量删除。

let workorderFetchRequest   = NSFetchRequest<NSFetchRequestResult>(entityName: "Workorders")
            let deleteWorkOrderRequest  = NSBatchDeleteRequest(fetchRequest: workorderFetchRequest) //Deletes ALL workorders
let context = DataController.sharedInstance().managedObjectContext

//Save the work order objects (overwriting any old ones with same id if needed)
            do {
                try context.execute(deleteWorkOrderRequest)
                context.reset()
                print(">>> cleared old data!")
            } catch let error as NSError  {
                print("Could not save \(error), \(error.userInfo)")
            } catch {
                print("Could not save, unknown error")
            }

这里的关键是理解批处理命令当前直接在数据库上工作并忽略托管上下文,这意味着我运行此命令后我的托管上下文和数据库不同步。简单的解决方法是在执行批处理命令后始终确保运行...

context.reset()

这将强制将数据从数据库加载回托管上下文,以便一切都同步。我做了这些改变之后一切正常。希望这有助于某人。