删除相互依赖的对象时了解核心数据
Understanding Core Data When Deleting Objects that Depend on Each Other
这个问题要求的是以下场景中的最佳实践:
附件是显示我的工作订单和服务核心数据实体的图像。请注意,删除规则当前为无工作订单操作。 (注意更改为 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"))
,这就是我知道它与服务相关的方式,并将工作单的规则更改为 级联删除 修复了崩溃,但是它删除了附加到它的服务!...这有道理,但不是我想要的。
我最近找到了我的问题的答案,这个问题与多件事有关。
首先我的核心数据堆栈设置不正确。我现在将其更改为这个(感谢我友好的开发人员朋友指出了这一点)。
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 设置设置为在 main thread 上工作。这也是问题的一部分,因为我 运行 在线程中编写我的代码。
在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()
这将强制将数据从数据库加载回托管上下文,以便一切都同步。在我进行这些更改后,一切正常。希望这对某人有所帮助。
这个问题要求的是以下场景中的最佳实践:
附件是显示我的工作订单和服务核心数据实体的图像。请注意,删除规则当前为无工作订单操作。 (注意更改为 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"))
,这就是我知道它与服务相关的方式,并将工作单的规则更改为 级联删除 修复了崩溃,但是它删除了附加到它的服务!...这有道理,但不是我想要的。
我最近找到了我的问题的答案,这个问题与多件事有关。
首先我的核心数据堆栈设置不正确。我现在将其更改为这个(感谢我友好的开发人员朋友指出了这一点)。
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 设置设置为在 main thread 上工作。这也是问题的一部分,因为我 运行 在线程中编写我的代码。
在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()
这将强制将数据从数据库加载回托管上下文,以便一切都同步。在我进行这些更改后,一切正常。希望这对某人有所帮助。