【问题标题】:Does every Core Data Relationship have to have an Inverse?每个核心数据关系都必须有一个逆关系吗?
【发布时间】:2010-10-20 07:57:25
【问题描述】:

假设我有两个实体类:SocialAppSocialAppType

SocialApp 中,我有一个属性:appURL 和一个关系:type

SocialAppType我有三个属性:baseURLnamefavicon

SocialApp 关系type 的目的地是SocialAppType 中的一条记录。

例如,对于多个 Flickr 帐户,会有许多 SocialApp 记录,每条记录都包含一个人帐户的链接。 “Flickr”类型将有一个SocialAppType 记录,所有SocialApp 记录都将指向该记录。

当我使用此架构构建应用程序时,我收到一条警告,指出 SocialAppTypeSocialApp 之间没有反向关系。

 /Users/username/Developer/objc/TestApp/TestApp.xcdatamodel:SocialApp.type: warning: SocialApp.type -- relationship does not have an inverse

我需要逆向吗?为什么?

【问题讨论】:

    标签: objective-c core-data


    【解决方案1】:

    Apple 文档中有一个很好的示例,该示例表明您可能会因为没有反向关系而遇到问题。让我们把它映射到这个案例中。

    假设您对其建模如下:

    请注意,您有一个名为“type”的一对一关系,从SocialAppSocialAppType。该关系是非可选的,并且有一个“拒绝”删除规则

    现在考虑以下几点:

    SocialApp *socialApp;
    SocialAppType *appType;
    // assume entity instances correctly instantiated
    
    [socialApp setSocialAppType:appType];
    [managedObjectContext deleteObject:appType];
    BOOL saved = [managedObjectContext save:&error];
    

    我们期望这个上下文保存失败,因为我们将删除规则设置为拒绝,而关系是非可选的。

    但这里保存成功。

    原因是我们没有设置反向关系。因此,当 appType 被删除时,socialApp 实例不会被标记为已更改。因此,在保存之前没有对 socialApp 进行验证(它假设不需要验证,因为没有发生任何更改)。但实际上发生了变化。但它没有得到反映。

    如果我们通过

    来回忆appType
    SocialAppType *appType = [socialApp socialAppType];
    

    appType 为零。

    很奇怪,不是吗?对于非可选属性,我们得到 nil?

    因此,如果您已经建立了反向关系,那么您就不会遇到麻烦。 否则,您必须通过编写如下代码来进行强制验证。

    SocialApp *socialApp;
    SocialAppType *appType;
    // assume entity instances correctly instantiated
    
    [socialApp setSocialAppType:appType];
    [managedObjectContext deleteObject:appType];
    
    [socialApp setValue:nil forKey:@"socialAppType"]
    BOOL saved = [managedObjectContext save:&error];
    

    【讨论】:

    • 这是一个很好的答案;感谢您从文档和我读过的所有内容中清楚地说明了什么是支持对所有事物进行逆运算的主要论点,即使在逆关系对人类没有意义的情况下也是如此。这确实应该是公认的答案。现在缺少的所有这个问题线程是对您可能选择 not 进行逆运算的原因的更详细探索,在 Rose Perrone 的回答中有所提及(以及在文档中,一点点)。
    • 希望有人能帮我解决这个问题。我的理解是,在源到目标的关系中,“拒绝”的删除规则意味着:“如果关系中有 any destination 对象,source 无法删除。”根据该定义,此示例并不完全适用。
    【解决方案2】:

    实际上,我没有因为没有逆向而丢失任何数据 - 至少我知道。一个快速的谷歌建议你应该使用它们:

    反向关系不仅仅 让事情更整洁,其实是 Core Data 用来维护数据 完整性。

    -- Cocoa Dev Central

    您通常应该建模 两个方向的关系,以及 指定逆关系 适当地。核心数据使用这个 确保一致性的信息 如果更改是对象图的 作出(见“操纵关系 和对象图完整性”)。为一个 讨论一些原因 你可能不想建模 两个方向的关系,以及 可能出现的一些问题 如果不这样做,请参阅“单向 关系。”

    -- Core Data Programming Guide

    【讨论】:

    • 见 MadNik 的回答(在我写这篇文章的那一刻的页面底部),以更全面地解释当文档说反向关系有助于保持数据完整性时的含义。
    【解决方案3】:

    我将解释我在 Dave Mark 和 Jeff LeMarche 的更多 iPhone 3 开发中找到的明确答案。

    Apple 通常建议您始终创建并指定逆向关系,即使您未在应用中使用逆向关系。因此,它会在您未能提供逆向关系时向您发出警告.

    关系不是要求有逆向关系,因为在少数情况下逆向关系可能会损害性能。例如,假设反向关系包含非常多的对象。移除逆需要遍历表示逆的集合,从而削弱性能。

    除非您有特定的理由不这样做,否则请逆向建模。它帮助 Core Data 确保数据完整性。如果遇到性能问题,以后消除反比关系相对容易。

    【讨论】:

      【解决方案4】:

      更好的问题是,“有理由有一个逆”吗? Core Data 实际上是一个对象图管理框架,而不是持久性框架。换句话说,它的工作是管理对象图中对象之间的关系。反向关系使这很多更容易。出于这个原因,Core Data 期望反向关系并且是为那个用例编写的。没有它们,您将不得不自己管理对象图的一致性。特别是,没有反向关系的多对多关系很可能会被 Core Data 破坏,除非您非常努力工作以保持工作正常。与它为您带来的好处相比,反向关系在磁盘大小方面的成本确实微不足道。

      【讨论】:

        【解决方案5】:

        至少有一种情况可以很好地为核心数据关系建立一个没有逆向的情况:当两个对象之间已经存在另一个核心数据关系时,它将负责维护对象图。

        例如,一本书包含许多页,而一页包含在一本书中。这是一个双向的多对一关系。删除页面只会使关系无效,而删除书籍也会删除页面。

        但是,您可能还希望跟踪每本书正在阅读的当前页面。这可以通过 Page 上的“currentPage”property 来完成,但是您需要其他逻辑来确保书中只有一个页面被标记为当前页面时间。相反,将当前页面 关系 从 Book 到单个页面将确保始终只有一个当前页面被标记,此外,可以通过简单的 book 参考书轻松访问此页面.currentPage.

        在这种情况下,互惠关系是什么?在很大程度上是荒谬的。 “myBook”或类似的东西可以在另一个方向添加回来,但它只包含页面的“book”关系中已经包含的信息,因此会产生自己的风险。也许在将来,您使用这些关系之一的方式会发生变化,从而导致您的核心数据配置发生变化。如果 page.myBook 已在代码中本应使用 page.book 的某些地方使用,则可能会出现问题。主动避免这种情况的另一种方法是不在用于访问页面的 NSManagedObject 子类中公开 myBook。但是,可以说,首先不对逆进行建模会更简单。

        在概述的示例中,currentPage 关系的删除规则应设置为“No Action”或“Cascade”,因为与“Nullify”没有相互关系。 (Cascade 意味着您在阅读时会撕掉书中的每一页,但如果您特别冷且需要燃料,这可能是真的。)

        当可以证明对象图完整性没有风险时(如本例所示),并且代码复杂性和可维护性得到提高,可以认为没有逆关系的关系可能是正确的决定。


        如 cmets 中所讨论的,另一种解决方案是在目标上创建自己的 UUID 属性(在此处的示例中,每个 Page 都有一个 UUID 的 id),将其存储为属性(currentPage 仅存储UUID 作为 Book 中的属性,而不是关系),然后在需要时编写一个方法来获取具有匹配 UUID 的 Page。这可能比使用没有逆关系的关系更好,尤其是因为它避免了所讨论的警告消息。

        【讨论】:

        • 谢谢邓肯——这与我的用例非常接近。本质上,我需要能够获得一本书的最后一页。我需要这是一个持久值,以便我可以在 fetch 谓词中使用它。我已经设置了一个“bookIAMLastPageOf”逆,但它非常荒谬并且导致其他问题(我不正确理解)。你知道是否有办法禁用单个案例的警告吗?
        • 有没有办法禁用警告?我现在有 2 个:...应该有一个相反的No Action Delete Rule ...可能会导致与已删除对象的关系。并且currentPage可以标记为Optional吗?
        • 将 currentPage 存储为 NSManagedObjectID 而不是关系是否有效?
        • 我的理解(很高兴得到纠正)是,您不能依赖 NSManagedObjectID 在应用重启和 iCloud/CloudKit 同步等事情上保持不变。特别是,我相信 NSManagedObjectID 在第一次创建对象时是不同的,但在对象第一次在保存操作中插入托管存储之前......另一种解决方案是在目标上创建自己的 UUID 属性,存储作为属性,然后使用匹配的 UUID 获取目标。这可能比使用没有逆关系的关系更好。
        【解决方案6】:

        虽然文档似乎不需要逆向,但我只是解决了一个实际上通过没有逆向而导致“数据丢失”的场景。我有一个与可报告对象有一对多关系的报告对象。如果没有反向关系,对多对关系的任何更改都会在重新启动时丢失。在检查了 Core Data 调试后,很明显,即使我保存了报表对象,也从未对对象图(关系)进行更新。我添加了一个逆,即使我不使用它,瞧,它有效。所以它可能不会说它是必需的,但没有逆关系的关系肯定会产生奇怪的副作用。

        【讨论】:

          【解决方案7】:

          一般不需要反向关系。但是核心数据中很少有需要反向关系的怪癖/错误。如果缺少反向关系,即使在保存上下文时没有错误,也存在关系/对象丢失的情况。检查这个example,我创建它是为了演示对象丢失以及如何解决,同时使用核心数据

          【讨论】:

            【解决方案8】:

            倒数也用于Object Integrity(出于其他原因,请参阅其他答案):

            推荐的方法是对两个方向的关系进行建模 并适当地指定反向关系。核心数据使用 此信息以确保对象图的一致性,如果 改变了

            发件人:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/HowManagedObjectsarerelated.html#//apple_ref/doc/uid/TP40001075-CH17-SW1

            提供的链接为您提供了为什么应该设置inverse 的想法。没有它,您可能会丢失数据/完整性。此外,您访问nil 对象的可能性更大。

            【讨论】:

              猜你喜欢
              • 2011-08-03
              • 1970-01-01
              • 2013-09-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多