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

Posted

技术标签:

【中文标题】删除相互依赖的对象时了解核心数据【英文标题】:Understanding Core Data When Deleting Objects that Depend on Each Other 【发布时间】:2016-11-04 05:29:56 【问题描述】:

这个问题要求在以下情况下的最佳实践:

附件是显示我的工作订单和服务核心数据实体的图像。请注意,删除规则当前是对工单无操作。 (注意更改为 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")
    

用于跟踪 coreData 值的上下文和全局变量在 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

核心数据模型参考:

我尝试过的其他注意事项和事情:

我知道它在这一行崩溃(workorder1.setValue(g_services[0], forKey: "service")),这就是我知道它与服务相关的方式,并且将规则更改为 cascade delete 用于工作订单修复了崩溃但是它删除了服务依附于它!...这是有道理的,但不是我想要的。

【问题讨论】:

【参考方案1】:

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

首先我的核心数据堆栈设置不正确。我现在把它改成了这个(感谢我友好的开发者朋友指出了这一点)。

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 中的 concurrency 设置被设置为在 主线程 上工作。这也是问题的一部分,因为我在线程中运行我的代码。

在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()

这会强制将数据库中的数据加载回托管上下文,以便一切都保持同步。在我进行这些更改后,一切正常。希望这对某人有所帮助。

【讨论】:

以上是关于删除相互依赖的对象时了解核心数据的主要内容,如果未能解决你的问题,请参考以下文章

DCI,“上下文”概念的问题以及相互了解的角色

Spring--1简单了解

java解耦

了解一对多的核心数据删除规则

了解核心数据并在删除后保存 NSManagedObjectContext

核心数据:如何删除不在新数据中的对象