核心数据:插入具有关系的数据时遇到问题

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&lt;NSNumber *, Category *&gt; 让您快速查找现有类别,而不是每次都访问数据库。这有意义/回答你的问题吗? 是的,这是有道理的。我会标记你问正确的答案。 感谢您的接受!如果您有后续问题,请发布后续问题! :)

以上是关于核心数据:插入具有关系的数据时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

在插入新的子对象时,父子对象传递与多级的一对多关系。 IOS核心数据

核心数据需要时间来插入具有获取实体的记录并设置为关系

具有现有关系奇怪问题的核心数据插入[重复]

具有多个映射模型的复杂核心数据迁移

将对象添加到具有多对多关系的核心数据中的 NSSet

将新的核心数据对象插入到多个 NSOrderedSet 会忽略索引