核心数据:插入具有关系的数据时遇到问题
Posted
技术标签:
【中文标题】核心数据:插入具有关系的数据时遇到问题【英文标题】:Core Data: having issue inserting data with relationship 【发布时间】:2017-03-23 13:40:34 【问题描述】:我有两个实体(类别、事件),它们具有双向多对多关系。一个类别可以有多个事件,一个事件可以有多个类别。从某种意义上说,类别与事件的关系是可选的,类别可以在没有事件的情况下存在,但事件与类别的关系是强制性的(没有类别,事件就不能存在)。我正在尝试插入事件并向它们添加类别,但出现 NSValidationErrorValue=Relationship 错误。这是我的代码:
private func storeEventsXMLStream(_ xml: XMLIndexer)
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator
// Remove all data before inserting
// This line of code is necessary because data needs to be downloaded on daily basis.
// Otherwise, I will get redundant data.
removeAllExistingData("Event_Ottawa", managedObjectContext: managedObjectContext)
autoreleasepool // Scoping is necessary to fix memory leak
for xmlcat in xml["events"]["event"]
let event = NSEntityDescription.insertNewObject(forEntityName: "Event_Ottawa", into: managedObjectContext) as! Event_Ottawa
event.id = Int32((xmlcat.element?.attribute(by: "id")?.text)!)!
event.website_url_english = xmlcat["website_url_english"].element?.text
event.website_url_french = xmlcat["website_url_french"].element?.text
// setting other attributes of events here. Exactly like I did in above 3 line
// Just another attribute. Storing it a String in Coredata
var recur_rules = ""
for rule in xmlcat["recur_rules"]["recur_rule"]
recur_rules += (rule.element?.attribute(by: "weekday")?.text)!
if !recur_rules.isEmpty
event.recur_rules = recur_rules
do
var predicateArray:[NSPredicate] = []
// Categories are inserted to the Coredata before this method call. So I'm fetching the applicable one here.
for category in xmlcat["categories"]["category"]
let predicate = NSPredicate(format: "id = %@", (category.element?.attribute(by: "id")?.text)!)
predicateArray.append(predicate)
let requestCategory:NSFetchRequest<Category_Event_Ottawa> = Category_Event_Ottawa.fetchRequest()
requestCategory.predicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicateArray)
let managedContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
managedContext.persistentStoreCoordinator = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator
let applicableCategories = try managedContext.fetch(requestCategory)
for category in applicableCategories
event.addToCategory(category)
catch
print(error)
// only save once per batch insert
do
try managedObjectContext.save()
catch
print(error)
managedObjectContext.reset() <-- I get EXC_BAD_ACCESS, when I use the same object context for fetching as insertion
我收到的错误:
Error Domain=NSCocoaErrorDomain Code=1560 "(null)" UserInfo=NSDetailedErrors=(
"Error Domain=NSCocoaErrorDomain Code=1550 \"The operation couldn\U2019t be completed. (Cocoa error 1550.)\" UserInfo=Dangling reference to an invalid object.=null, NSValidationErrorValue=Relationship 'category' on managed object (0x6100050963f0)
如果对插入和提取使用相同的托管对象上下文,我不会再收到此错误。但是我从重置上下文对象的行中得到 EXC_BAD_ACCESS。我从另一篇文章中了解到 CoreData 不是线程安全的。所以也许这是一个问题。但是我该如何解决这个问题?如果相关,Event-Category 关系的删除规则为 Nullify,Category-Event 为 Cascade。
【问题讨论】:
一些问题/cmets: 1) 当您向数据模型文件添加新关系时,Optional
属性默认处于选中状态 - 您在某个时候没有取消选中它吗?这将是您收到关系错误的最简单原因。 2) 无论错误如何,当您在上下文中调用reset()
时,您尝试做什么/期望发生什么?
3) 我仍然熟悉使用 NSPersistentContainer
类,但是有什么理由你以这种方式创建上下文而不是使用 PersistentContainer.viewContext
作为主要上下文并使用 newBackgroundContext()
创建示例代码显示的背景上下文?
4) 重新线程安全,对于在后台/私有线程上工作的任何上下文,您应该在上下文的perform
(异步)或performAndWait
(同步)闭包内执行操作,以防止线程-相关错误developer.apple.com/reference/coredata/nsmanagedobjectcontext/…
@MatthewS : 1 ) 我确保已设置关系选项以反映我的需求。所以是的,Category->Event 是可选的,Event->Category 不是可选的。 2)我想确保当我再次执行coredata相关操作时没有旧数据的痕迹留在内存中。 3) 我也是 NSPersistentContainer 的新手。我的代码基于我从 SO 中学到的帖子。我读到这种方法可以让我在后台而不是 UI 线程中执行,并且我有 200k 行要插入。我不知道这种方法和你的方法有什么区别。我必须调查一下。并感谢您的链接!
创建上下文的方式可能并没有什么不同——我只是没这么看......所以可能是一个红鲱鱼。但我认为,如果您在 managedObjectContext.perform
闭包范围内调用 reset()
,那么我认为这将防止 BAD_ACCESS 错误。此外,当您一次导入 100k+ 条这样的记录时,还要确保您尝试批量大小(例如,每 1000 个对象调用保存/重置以保持低内存使用率)
【参考方案1】:
在这一行:
requestCategory.predicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicateArray)
您正在使用 AND 创建复合谓词。您要求 Core Data 为您获取所有具有 id = 7 AND id = 8 AND ... 的类别。这是行不通的。类别只能有一个 id。在这种情况下,您实际上需要一个“或”谓词。
但是,我认为更好的方法是在开始循环遍历 XML 之前将所有类别加载到以它们的 id 为键的字典中,然后将它们从字典中拉出。这将比每次获取性能要高得多。
此外,您不能在单独的上下文中获取类别,然后在不同的上下文中创建对象之间的关系。如果你尝试,Core Data 会崩溃。
【讨论】:
感谢您指出这一点。因为这可能是我出现各种奇怪症状的原因之一(点击类别元素时 UI 停止响应等)。但我还有一个问题。使用核心数据生成的 nsmanaged 类来存储您建议的 Dictionary 的类别数据是一种好方法吗?还是我应该为此声明新的 Category 类? 您现有的 Category 实体对我来说很有意义。我只是建议NSDictionary<NSNumber *, Category *>
让您快速查找现有类别,而不是每次都访问数据库。这有意义/回答你的问题吗?
是的,这是有道理的。我会标记你问正确的答案。
感谢您的接受!如果您有后续问题,请发布后续问题! :)以上是关于核心数据:插入具有关系的数据时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章