核心数据模型设计 - 更改“实时”对象也会更改保存的对象
Posted
技术标签:
【中文标题】核心数据模型设计 - 更改“实时”对象也会更改保存的对象【英文标题】:Core Data Model Design - Changing "Live" Objects also Changes Saved Objects 【发布时间】:2010-03-29 19:39:34 【问题描述】:我正在开发我的第一个 Core Data 项目(在 iPhone 上),我非常喜欢它。 Core Data 是很酷的东西。
然而,我遇到了一个我不确定如何解决的设计难题,尽管我认为这是一个相当普遍的情况。它涉及数据模型。
为了清楚起见,我将使用一个虚构的足球游戏应用作为示例来说明我的问题。假设有NSMO,称为Downs and Plays。播放 Downs 使用的模板等功能。用户创建 Plays(例如,Bootleg、Button Hook、Slant Route、Sweep 等)并填写各种属性。戏剧与唐斯有一对多的关系。对于每个 Down,用户决定使用哪个 Play。当 Down 被执行时,它使用 Play 作为它的模板。每次 down 运行后,它都存储在历史记录中。该程序会记住所有播放过的 Downs。
到目前为止,一切都很好。这一切都很好。
我的问题是当用户想要更改 Play 的详细信息时会发生什么。假设它最初涉及向左的传球,但用户现在希望它成为向右的传球。但是,进行该更改不仅会影响该 Play 的所有未来执行,还会更改存储在历史中的 Play 的详细信息。实际上,Downs 的记录被“污染”了,因为 Play 模板已更改。
我一直在尝试几种可能的解决方案来解决这种情况,但我想 SO 的天才们比我更了解如何处理这种情况。尽管如此,我想出的潜在修复是:
剧本的“版本控制”。对 Play 模板的每次更改实际上都会创建一个具有相同名称的新的、单独的 Play 对象(据用户所知)。然而,在引擎盖下,它实际上是一个不同的游戏。这会起作用,AFAICT,但似乎它可能会导致 Play 对象的疯狂扩散,尤其是。如果用户不断在同一个 Play 的多个版本之间来回切换(每次用户切换时创建一个又一个对象)。是的,该应用程序可以检查预先存在的、相同的 Play,但是……它看起来就像一团糟。
Have Downs 在保存时记录他们使用的 Play 的详细信息,但不作为 Play 对象。这看起来很荒谬,因为 Play 对象是存在来保存这些细节的。
认识到 Play 对象实际上实现了 2 个功能:一个是 Down 的模板,另一个是记录使用的模板。这两个函数与 Down 有不同的关系。第一个(模板)具有一对多的关系。但是第二个(记录)具有一对一的关系。这将意味着创建第二个对象,例如“Play-Template”,它将保留与 Downs 的多对多关系。 Play 对象将被重新配置为与 Downs 具有 一对一 关系。 Down 将使用 Play-Template 对象执行,但使用新类型的 Play 对象来存储使用的模板。正是这种从一对多关系到一对一关系的变化代表了问题的症结所在。
即使把这个问题写出来也让我变得更清楚了。我认为像解决方案 3 这样的东西就是答案。但是,如果有人有更好的主意,甚至只是确认我走在正确的轨道上,那将很有帮助。 (请记住,我并不是真的在制作足球游戏,只是使用每个人都理解的比喻更快/更容易。)
【问题讨论】:
【参考方案1】:我认为你需要重新开始你的设计。
(1) 为什么使用 PlayEntity 作为 DownEntity 的模板?实体实际上是(在引擎盖下)类,因此类定义本身就是每个实例的“模板”。
(2) Managed Objects 应该代表真实对象或真实信息关系的数据模型。因此,您需要认真考虑您要建模的真实对象或信息。一个好的开始是问问自己如何用笔和纸记录这些信息。
在您的示例中,Plays 和 Downs 建模完全不同的事物。
Down 是按时间排序的事件。在任何特定游戏中只有一个特定的 Down。这意味着足球史上每场比赛中的每一场比赛都是独一无二的。因此,Down 数据模型实体将主要对在时间上对 Down 与其他 Downs 和整个游戏的关系建模感兴趣。
相比之下,戏剧是一种空间事件。游戏不是唯一的,并且经常在游戏中以及从游戏到游戏重复。一个 Play 实体应该关注球员、球和场地之间的空间关系。
你会得到这样的结果:
DownEntity
game;
half;
quarter;
turnover;
gameClockTime;
yardLine;
penalties;
play --(required,Cascade)->PlayEntity.down
previousDown --(optional, nullify)-->Down.nextDown;
nextDown --(optional, nullify)-->Down.previousDown
PlayEntity
playName;
//whatever other detail you want to model
down --(optional,nullify)-->>DownEnity.play;
请注意,两个实体都不会复制另一个实体的属性中保存的信息。它们也不共享继承,因为它们没有对游戏的相同方面进行建模。 Down 模拟时间序列,而 Play 模拟空间序列。这需要他们两个完整地描述每次倒下时发生的事情。
您可以通过首先创建任何您想要的标准化 PlayEntities 来构建您的数据库。如果您有一部新剧,您将创建一个新的 PlayEntity 并根据需要填充它。每次您遇到问题时,您都会创建一个 DownEntity 并创建与现有或新创建的 PlayEntity 的关系。
【讨论】:
可能是我对当前设计的描述不够详细,引起了误解。你所描述的都已经在我的设计中了。甚至是一开始的标准化“PlayEntities”。您的模拟 Down and Play 实体看起来与我已经拥有的实体基本相同。 这里的设计问题与我上面概述的问题相同。如果您更改 PlayEntity 的详细信息,它会更改所有已保存 Downs 中(Play Entity)的详细信息,从而将它们破坏为所发生事件的真实历史。 IE。它说使用的 PlayEntity 现在实际上与使用的略有不同。 如果每次播放都可以不同,那么模型最好通过为每个 Down 创建不同的 PlayEntity 来反映这一点。没有令人信服的理由来重用 PlayEntities,除非它们实际上在模型的限制范围内完全复制。您可以将模板数据存储在其他位置以创建相似的剧本和/或您可以简单地将使用相同基本形式的剧本命名为相同,这样您就可以搜索所有 play.name == "Right42 的 Downs。但是,每个PlayEntity 的实际实例将是唯一的,即使它的属性与另一个相同。 我相信这就是我在问题的第 3 部分中建议的内容:拥有 Play-Templates 和 Plays(或者,正如您所拥有的 Play-TemplateEntities 和 PlayEntities) . Play-Templates 可以重复使用,但 Plays 是独一无二的。我喜欢你关于名称搜索的想法,因为它有助于解决 #3 解决方案的一个缺点(如果 Plays 是独一无二的,那么很难获取诸如“使用 Play X 的所有问题”之类的内容)。 是的,您应该分解实体以反映您正在建模的现实的不同部分。由于模板播放和每个实际向下的实际播放是完全独立的现实世界对象(第一个在书中,第二个发生在场上)它们应该都有一个单独的实体。人们在开始使用 Core Data 时犯的最大错误之一就是试图将尽可能多的功能塞进每个实体中。相反,您应该做相反的事情,将模型的每个部分分解为尽可能小的实体。【参考方案2】:我会选择您的 #3 作为最清晰、最明智的选择。它很好地涵盖了您的(隐喻)应用程序的预期用途。
不久前,我在使用一个跟踪对人们进行的测试的应用程序时遇到了类似的情况;测试包含多个问题,并且可以在多个日期更改和重新管理。拥有一个测试模板以及单独的测试对象,使整个模型更容易处理。
【讨论】:
以上是关于核心数据模型设计 - 更改“实时”对象也会更改保存的对象的主要内容,如果未能解决你的问题,请参考以下文章