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

标签 swift core-data swift3 ios10 nsfetchrequest

此问题询问以下场景中的最佳实践:

随附的图像显示了我的工作订单和服务核心数据实体。请注意,删除规则当前为工单无操作。 (请注意,更改为 Nullify 不会解决我的问题,只会导致相同的问题)。另请注意,在服务上我对 id 有限制。这不允许重复。因此,我添加了以下合并策略:

context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

合并策略将采用我发送的新数据并覆盖数据库中的默认数据。如果没有这个,我的程序将抛出一个错误及其编写方式。

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

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

我的代码发生在我的一个 Controller 的 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
}

核心数据模型引用: enter image description here

enter image description here

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

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

最佳答案

我最近找到了我的问题的答案,并且该问题与多个因素有关。

首先,我的核心数据堆栈设置不正确。我现在已将其更改为此(由我友好的开发人员 friend 指出)。

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

另一件事需要注意的是数据 Controller 中的并发设置被设置为在主线程上工作。这也是问题的一部分,因为我在线程中运行代码。

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

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

关于swift - 删除相互依赖的对象时了解核心数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40415856/

相关文章:

xcode - 避免核心数据获取的重复结果

ios - AFNetworking 在调用者的线程中自动调度完成 block

arrays - 检查 Swift 数组是否包含对象实例

ios - 进度警报不会从 Swift 3 中的 View 中消失

swift UIStackView : Positioning elements from center programmatically

swift - CornerRadius 恰好是 UIView

ios - 多个导航 Controller 之间的导航

arrays - Swift 数组 reversed()[n] 是否有效?

iphone - 在 NSManagedObjectContext 中调试 SIGABRT -save :

ios - 使用 - observeValueForKeyPath :ofObject:change:context: in Swift 3