删除Invoice父记录时为何数组嵌套的InvoiceItems未被同步删除?
核心问题
- 业务场景:自研
Invoice类包含由自定义InvoiceItems类组成的关联数组,预期删除Invoice主记录时自动清除所有关联的发票条目,实际执行删除操作后,数据库中仍残留对应的发票条目记录 - 认知偏差:认为只要在
Invoice中用Array字段存储关联内容、而非Relation或Pointer类型字段,就会在父记录删除时自动移除嵌套关联项 - 现有实现代码:
func removeInvoice(with invoice: Invoice, completion: @escaping (Result<Bool, NSError>) -> ()) { guard let invoiceObjectId = invoice.getPFInvoice()?.objectId else { let error = NSError(domain: "OURTEAM", code: 0, userInfo: [NSLocalizedDescriptionKey: "Remove invoice failure"]) completion(.failure(error)) return } let query = PFQuery(className: "Invoice") query.whereKey("objectId", equalTo: invoiceObjectId) query.findObjectsInBackground { (objects, error) in if let unwrappedError = error as NSError? { completion(.failure(unwrappedError)) } if let unwrappedObjects = objects { for object in unwrappedObjects { object.deleteEventually() } completion(.success(true)) } } }
问题根因
Parse SDK的存储规则里,哪怕你把InvoiceItems的对象实例直接作为数组元素赋值给Invoice的字段,服务端也只会为这些子对象存储对应的Pointer引用,不会把子对象的完整数据作为独立JSON块永久内嵌在父对象的存储行中。这些InvoiceItems本质是独立存放在对应数据表中的单独记录,服务端不会在父记录被删除时执行任何自动级联清理操作。
换句话说,你以为Array字段存的是全量内嵌的嵌套数据,实际这个字段只是存了一串指向独立InvoiceItems记录的指针集合,删除父级Invoice时,系统没有任何机制会联动删除这些指针指向的独立行记录。
修复方案
- 查询目标Invoice记录时,通过
includeKey指定关联的InvoiceItems字段,确保查询返回的对象已经加载全量关联数据,不会出现字段值为空的情况 - 拿到目标Invoice对象后,先取出字段中存储的所有关联
InvoiceItems对象 - 批量调用系统提供的
deleteAllEventually/deleteAllInBackground方法,先完成所有关联条目的删除 - 等关联条目全部删除完成后,再执行主Invoice对象的删除操作
- 不要在刚触发删除流程时就提前回调成功状态,要等所有删除操作真正执行完成后再触发回调
修正后的实现参考:
func removeInvoice(with invoice: Invoice, completion: @escaping (Result<Bool, NSError>) -> ()) { guard let invoiceObjectId = invoice.getPFInvoice()?.objectId else { let error = NSError(domain: "OURTEAM", code: 0, userInfo: [NSLocalizedDescriptionKey: "Remove invoice failure"]) completion(.failure(error)) return } let query = PFQuery(className: "Invoice") query.whereKey("objectId", equalTo: invoiceObjectId) // 关联查询出InvoiceItems字段的完整数据 query.includeKey("invoiceItems") query.getFirstObjectInBackground { (object, error) in if let unwrappedError = error as NSError? { completion(.failure(unwrappedError)) return } guard let invoiceObject = object else { let error = NSError(domain: "OURTEAM", code: 1, userInfo: [NSLocalizedDescriptionKey: "Target invoice record not found"]) completion(.failure(error)) return } // 取出关联的发票条目数组 guard let associatedItems = invoiceObject["invoiceItems"] as? [PFObject] else { invoiceObject.deleteEventually { completion(.success(true)) } return } // 先删除所有关联条目 PFObject.deleteAllEventually(associatedItems) { // 关联条目删除完成后,再删除主发票记录 invoiceObject.deleteEventually { completion(.success(true)) } } } }
常见踩坑提醒
原有代码的逻辑漏洞:查询到匹配对象后立刻触发删除流程并回调成功,异步的删除操作此时才刚在后台队列排队,一旦应用被强制退出,不仅关联的发票条目会残留,甚至主Invoice记录都可能没有完成删除。删除操作的回调时机一定要放在所有删除动作真正落库之后,不要提前返回成功状态。
内容的提问来源于stack exchange,提问作者Oleksandr Matrosov
相关产品推荐
相关产品推荐

