核心数据模型devise

假设我有一个关于烹饪食谱的应用程序,有两个基本function:

  1. 第一个涉及我正在准备的CURRENT配方
  2. 第二个存储我决定保存的食谱

标准情景

我目前的食谱是“芝士蛋糕”,在RecipeDetailViewController我可以看到我为这个食谱添加的当前成分:

  • 牛奶
  • 牛油
  • 等等

好吧,让我们说,我从最后的结果满意,我决定保存(logging)我刚刚准备的食谱。

* 点击保存 *

配方现在保存(现在logging),并在RecipesHistoryViewController我可以看到这样的事情:

  • 2013年11月15日 – 芝士蛋糕
  • 2013年11月11日 – 布朗尼
  • 等等

现在,如果我想要的话,我可以编辑 历史中的配方,例如,将牛奶改为豆奶。

在历史中编辑配方的问题不应该在我当前的配方中编辑配方(及其配料),反之亦然。 如果我编辑当前配方并用花生酱代替黄油,则不得编辑历史中存储的任何配方。 希望我解释一下自己。

后果

这种情况意味着什么? 意味着目前,为了满足这个function的function,每当用户点击“保存配方”button,我就复制配方和每个子关系(配料)。 那么它的作品,但我觉得它可以是更清洁的东西。 有了这个实现,事实certificate,我有不同的重复核心数据对象(sqlite行)吨这样的:

  • 对象#1,名称:黄油,配方:1
  • 对象#2,名称:黄油,食谱:4
  • 对象#3,名称:黄油,配方:3

等等

想法? 我怎样才能优化这个模型结构?

编辑1

我已经想过用属性NSString创build任何RecipeHistory对象,我可以存储一个json字典,但我不知道它是否更好。

编辑2

目前RecipeHistory对象包含这个:

 +-- RecipeHistory --+ | | | attributes: | | - date | +-------------------+ | relationships: | | - recipes | +-------------------+ +----- Recipe ------+ | relationships: | | - recipeInfo | | - recipeshistory | | - ingredients | +-------------------+ +-- RecipeInfo ----+ | | | attributes: | | - name | +-------------------+ +--- Ingredient ----+ | | | attributes: | | - name | +-------------------+ | relationships: | | - recipe | +-------------------+ 

paulrehkugler是真的,当他说当我创buildRecipeHistory复制每个Recipe对象(及其关系RecipeInfo和成分)将填补数据库吨数据,但我没有find另一个解决scheme,让我灵活的未来。 也许将来我会创build关于食谱和历史的统计数据,并且核心数据对象可能被certificate是有用的。 你怎么看? 我认为这是很多存储历史并允许编辑历史logging的应用程序的常见情况。


大更新

我已经阅读了一些用户的答案,我想解释更好的情况。 我上面的例子只是一个例子,我的意思是我的应用程序不涉及厨师/食谱的论点,但我已经使用食谱,因为我认为这对我的真实情况是相当好的。

说我想解释的是,应用程序需要两个部分: – 第一:在哪里我可以看到CURRENT配方与相关成分 – 第二:在哪里我可以看到食谱,我决定通过点击一个button“保存食谱”部分

在第一部分中find的当前配方和在“历史”部分中find的X配方没有任何共同之处。 然而,用户可以编辑保存在“历史”部分中的任何食谱(他可以编辑姓名,配料,他想要的任何东西,他可以完全编辑关于在历史部分find的食谱的所有内容)。

这就是我复制所有NSManagedObject的原因。 但是,这样,数据库会变得疯狂,因为每当用户保存当前配方时,表示配方( Recipe )的对象被复制,并且配方(配料)的关系也被复制。 所以会出现一些名为“黄油”的成分。 你可以说:为什么你需要有吨的“黄油”物体? 那么,我需要它,因为成分有例如“数量”属性,所以每个配方有不同数量的成分。

无论如何,我不喜欢这种方法,即使它似乎是唯一的。 问我任何你想要的,我会尽力解释每一个细节。

PS:对不起我的基础英语。

编辑

在这里输入图像说明

由于您必须处理历史,并且由于事件是由最终用户手动生成的,因此请考虑更改方法:不是存储模型实体的当前视图(即配方,配料以及它们之间的连接),而是存储启动的单个事件由用户。 这就是所谓的事件采购

这个想法是logging用户做了什么,而不是在用户的行为之后logging新的状态。 当您需要获取当前状态时,“重放”事件,将更改应用于内存中的结构。 除了让你执行即时的要求,这将让你恢复到特定的date状态,通过“重播”事件到一定的date。 这有助于审计。

你可以通过定义像这样的事件来做到这一点:

  • CreateIngredient – 添加新的成分,并给它一个唯一的ID。
  • UpdateIngredient – 更改现有成分的属性。
  • DeleteIngredient – 从当前状态删除配料。 删除一个成分将从所有配方和配方历史中删除它。
  • CreateRecipe – 添加一个新的配方,并给它一个唯一的ID。
  • UpdateRecipeAttribute – 更改现有配方的属性。
  • AddIngredientToRecipe – 将配料添加到现有配方中。
  • DeleteIngredientFromRecipe – 从现有配方中删除配料。
  • DeleteRecipe – 删除配方。
  • CreateRecipeHistory – 从特定配方创build新的配方历史logging,并为历史logging提供一个新的ID。
  • UpdateRecipeHistoryAttribute – 更新特定配方历史logging的属性。
  • AddIngredientToRecipeHistory – 将配料添加到配方历史中。
  • DeleteIngredientFromRecipeHistory – 从配方历史logging中删除配料。

您可以使用Core Data API将单个事件存储在单个表中。 添加一个按顺序处理事件的类,并创build模型的当前状态。 这些事件将来自两个地方 – 由Core Data支持的事件存储和用户界面。 这可以让你保留一个单一的事件处理器,以及与食谱,配料和配方历史的当前状态的细节单个模型。

只有当用户查阅历史logging时才能重播事件,对吗?

不,那不会发生什么情况:您将启动时的整个历史logging读入当前的“视图”,然后将新的事件发送到视图和数据库以进行持久化。

当用户需要查阅历史logging(具体来说,当他们需要了解模型如何看起来像过去的特定date时),则需要部分重放事件,直到感兴趣的date为止。

由于这些事件是手工生成的,所以不会有太多的事情发生。我估计最多的是数千人,那就是100个食谱,每个食谱有10个成分。 在现代硬件上处理事件应该在几微秒内,因此读取和重放整个事件日志应该在毫秒之内。

此外,您是否知道任何显示如何在核心数据应用程序中使用事件采购的链接的链接? […]例如,我是否需要摆脱RecipeHistory NSManagedObject?

我不知道iOS上的事件采购的一个很好的参考实现。 这与其他系统上的实现并没有太大区别。 你需要摆脱你现有的所有表格,用一个如下所示的表格replace它:

事件日志条目

属性如下:

  • EventId – 此事件的唯一ID。 这是在插入时自动分配的,并且永远不会改变。
  • EntityId – 由此事件创build或修改的实体的唯一ID。 该ID由Create...处理器自动分配,并且不会更改。
  • EventType – 表示此事件types名称的短string。
  • 事件时间 – 事件发生的时间。
  • EventData – 事件的序列化表示 – 可以是二进制或文本的。

最后一项可以replace为表示由上述12个事件types使用的属性的超集的“非规格化”列组。 这完全取决于你 – 这张表只是存储你的事件的一种可能的方式。 它不一定是核心数据 – 事实上,它甚至不需要在数据库中(尽pipe它使事情变得更容易一些)。

我认为当RecipesHistoryViewController的一行被选中进行修改时,我们可以使用两个选项优化Save过程:

  • 让用户select是否必须保存新行或者可能发生更新。
    有一个Save Newbutton来在Recipe创build一个新的行和一个Updatebutton来更新当前选定的行。
  • 要跟踪对配方所做的更改(发生更新时),我将尝试仅logging配方的更改。
    使用EAV模式将是一个选项。

    在这里输入图像描述]![在这里输入图片描述!] [在这里输入图片描述 作为一个提示:
    配方名称的逗号分隔值可以用作新旧值,在RecipeHistory表中插入一行时, 样本可能会有所帮助。

关于大更新
假设真正的应用程序有持久性操作的数据库,一些build议可能会有所帮助。

在第一部分中find的当前配方和在“历史”部分中find的X配方没有任何共同之处

导致CurrentIn-History配方之间没有关系的自然方式,所以尝试创build一个关系将是徒劳的。 没有关系的devise将不会是正常的forms,冗余将是不可避免的。
stream动的方法会有很多logging,在这种情况下

  • 我们可以限制任何用户保存的食谱预定义的数量。

  • 优化配方表性能的另一个解决scheme是根据创builddate字段(涉及数据库pipe理员)对表进行范围分区 。

  • 另一个build议是有一个单独的成分概念表。
    ingredientreciperecipe-ingredient表将减less冗余。
    在这里输入图像说明

使用NoSql
如果关系不是应用程序逻辑中不重要的部分,那么我的意思是,如果你不打算在复杂的查询中结束,比如“哪些成分在Y成分总数less于Y的配方中使用了超过X倍,牛奶不是他们“或分析程序,然后看看NoSql数据库和他们的比较 。

它们提供了非关系,分布式,开源,无模式,简单的复制支持,简单的API,大量的数据和横向扩展。

有关基于文档的数据库的基本示例:
在我的本地机器(端口号5984)上安装couchdb ,在couchdb上创build食谱数据库(表),将通过发送标准HTTP请求( 使用curl )来完成,例如:

 curl -X PUT http://127.0.0.1:5984/recipe 

滴食谱表:

 curl -X DELETE http://127.0.0.1:5984/recipe 

添加配方:

 curl -X PUT http://127.0.0.1:5984/recipe/myFirstRecipe -d '{"name":"Cheese Cake","description":"i am using couchDB for my recipes", "ingredients": [ "Milk", "Sugar" ],}' 

获取myFirstRecipelogging(文档)

 curl -X GET http://127.0.0.1:5984/recipe/myFirstRecipe 

不需要像对象关系映射,数据库驱动程序等经典的服务器端过程
顺便说一句,使用Nosql将有短的进展,你需要考虑,像这里和这里 。

正如我所看到的,你的问题比模型结构更具概念性。
我对你的模型的想法是:

+ * ** * *** +
食谱
—————–

—————–
特性:
—————–
– isDraft – BOOL
– 名称 – NSString
– creationDate – NSDate
—————–

—————–
关系:
—————–
– 配料 – 一对多配料
—————–
+ * ** * *** +

+ * ** * *** +
成分
—————–

—————–
特性:
—————–
– 名称 – NSString
—————–

—————–
关系:
—————–
– 食谱 – 与食谱对多
—————–

+ * ** * *** +

现在,让我们打电话给你的“当前”配方草案(用户可能有很多草案)。
正如你所看到的,你现在可以用一个提取的结果控制器(FRC)显示你的配方,
获取请求将如下所示:

 NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"]; [r setFetchBatchSize:25]; NSSortDescriptor* sortCreationDate = [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]; [r setSortDescriptors:@[sortCreationDate]]; 

您可以在isDraft属性中分段数据:

 NSFetchedResultsController* frc = [[NSFetchedResultsController alloc] initWithFetchRequest:r managedObjectContext:context sectionNameKeyPath:@"isDraft" cacheName:nil]; 

请记住为您的部分提供适当的标题,以免混淆用户。

现在,你剩下的只是添加一些特定的function,如:

  • 创造新的食谱
    • 保存
    • 保存草稿
  • 编辑食谱(草稿或不)
    • 如果草稿要保存为完整的配方
    • 否则,保存实际的配方
    • 如果你喜欢,你可以添加一个“另存为”选项
  • 创build副本(用户意识到,如果他多次保存相同的配方,他可能会引入冗余数据)

无论如何,用户体验应该是一致的。
含义:
当用户正在编辑/添加一个对象时,这个对象不应该改变“在他的脚下”。
如果用户正在添加新的配方,则他可能希望将其保存为草稿或作为完整的配方。
无论在哪种情况下,他都可以继续编辑。 所以不需要创build新的对象。

如果您想为食谱添加版本控制,则需要添加与单个食谱相关的RecipeHistory类的实体。 这个实体将logging每一个完整的配方对象中的每个提交的变化的变化(使用NSManagedObject changedValues或检查现有/提交的值)。
您可以按照您认为合适的方式连续存储数据。

因此,您可以看到,它更像是一个概念性问题(您如何访问数据),而不是build模问题。

有几个问题需要回答:

  1. 一个配方的“历史项目”数量是否有限制,或者是否真的有必要保持配方的所有版本?
  2. 什么时候修改只是改变现有的配方,什么时候改变导致新的配方? 例如,是否允许使用者将“芝士蛋糕”食谱改为“肉饼”配方,完全取代每一种成分和标题?

在规划数据模型时,这些问题的答案很重要。 例如,问问自己这是否会成为您应用程序的有效用例:用户创build一个包含糖,面粉和鸡蛋的“基本蛋糕”配方。 用户现在想要把这个“基本蛋糕”配方作为模板来创build“芝士蛋糕”,“磅蛋糕”和“胡萝卜蛋糕”配方。 这是一个有效的用例吗?

如果是这样,每次你保存一个配方,它基本上创build一个全新的,独立的食谱,因为用户可以改变一切 ,从而把芝士蛋糕变成肉块。

不过,我认为这对用户来说是意想不到的行为。 在我看来,用户创build了一个“芝士蛋糕”配方,然后可能想跟踪对这一个配方的变化,而不是把它变成完全不同的东西。

这是我会build议的:

  1. RecipeHistory拥有Recipes ,而不是RecipeHistory ,改变你的数据模型,使Recipes有多个RecipeVersions 。 这样,用户可以明确地创build新的配方,然后跟踪对该配方的更改。 此外,用户不会被允许直接编辑RecipeVersion ,而是可以将他们的配方“还原”到特定的版本,然后进行编辑。
  2. 使Ingredients独一无二:“黄油”,“牛奶”和“面粉”在数据库中只存在一次,仅供参考。 这样,你的数据库中就不会有重复的内容,保存引用将比重复保存原料的名字占用更less的磁盘空间。
  3. 允许用户根据现有配方(版本)创build新配方。 通过这种方式,您可以让用户在现有应用程序中“基础化”新配方,而不会使应用程序和数据模型复杂化。

这是我build议的数据模型:

 +----- Recipe ------+ | attributes: | | - name | | relationships: | | - recipeVersions | +-------------------+ +-- RecipeVersion ----+ | attributes: | | - timestamp | +----------------------+ | relationships: | | - recipe | | - ingredients | +----------------------+ +--- Ingredient ----+ | attributes: | | - name | +-------------------+ | relationships: | | - recipeVersions | +-------------------+ 

请享用。

你不需要复制所有的配料对象。 相反,只是改变关系,使食谱有许多配料和成分可以在许多食谱。 然后,当你创build一个重复的食谱,你只需连接到现有的成分。

这也将使列出使用(或某些组合)成分的食谱更容易。

你还应该考虑你的用户界面/用户体验 – 它应该是一个完整的重复? 或者你应该允许用户在每个配方(只列出一组替代配料)中创build“替代品”。

这是存储大小和检索时间之间的折衷。

如果每次用户单击“保存配方”button时都复制每个配方,则会复制数据库中的大量数据。

如果您创build了一个RecipeHistory对象,其中包含一个Recipe和一系列更改,则检索数据和填充View Controller需要更长的时间,因为您必须在内存中重新创build一个完整的Recipe。

我不确定哪个更容易 – 无论哪个适合您的用例可能是最好的。

不知道我对你正在解决的问题很清楚,但是我会从build模食谱和配料开始,并将它们与实际的混合和方法分开,这可能随着厨师的实验而改变。 有了一些聪明的应用程序逻辑,您只能跟踪每个版本中的更改,而不是创build新的副本。 例如,如果用户决定尝试新版本的配方,则默认显示以前的版本(或允许用户select版本)。方法和配方成分以及是否作了任何更改,将这些更改保存为与方法和配方相关联的新方法RecipeVersion。

这种方法将使用较less的存储空间,但需要更复杂的应用程序逻辑,例如,交换一个成分就会将被replace的数量设置为0,并为新数据添加新的logging。 简单地复制以前的(或用户select的)版本不会占用太多的空间,这些都是小logging,而且实现起来要简单得多。

在这里输入图像说明

我相信最好定义成分表有成分ID和成分显示名称,并在收件人历史表中存储RecipieID,HistoryDate,IngredientArray。

如果在配料表中,id:1是Butter id:2是Milk id:3是cheese id:4是sugar id 5是豆浆机

然后在配方1的历史表中:芝士蛋糕,数据11月15日,成分数组:{1,2,3,4}如果在11月16日芝士蛋糕改变成豆奶而不是牛奶,那么在那一天成分arrays是{1,2 ,3,5}。 许多数据库都有数组列选项,或者可以是逗号分隔的string或Json文档。 最好将内容列表保存在内存中,以快速查找列表中的成分名称。

也许我不明白你的问题,但是你需要通过编辑来改变黄油的名字吗? 为什么不从一个食谱中删除黄油,并添加花生酱。 那样的话,你不会把黄油换成花生酱,因为你的其他食谱是与其相关的? 而新的食谱,你可以select花生酱或黄油。

只是要清楚,我们正在谈论前端?

首先,像Mohsen Heydaribuild议的那样,在SQL rdbms上,您应该在多对多连接之间创build一个表格,以便在性能上实现一对多。

所以你想要一个历史性的

 +-- RecipeHistory --+ | | | attributes: | | - id | | - date | | - new name? | | - notes ?? | | - recipe-id | +-------------------+ | relationships: | | - recipes | +-------------------+ +----- Recipe ------+ | attributes: | | - id | | - name | | - discription | | - date | | - notes | #may be useful? | - Modifiable | #this field is false if in history, else true, +-------------------+ | relationships: | | recipe-ingredient | +-------------------+ +-Recipe-ingridient-+ | attributes: | | id | | recipe-id | | ingridient-id | | quantity | +-------------------+ +--- Ingredient ----+ | | | attributes: | | - id | | - name | +-------------------+ | relationships: | | -recipe-ingredient| +-------------------+ 

现在,如果Recipe = True的可修改字段属于MainPage

如果它是虚假的,它就属于历史性的页面

find您想要的配方后,您可以使用配方成分表或配料配方按同样的方法查询配方的配方。

另一个选项空间更小,将创build一个配方历史logging,并创build一个修改的配方表 – >这需要一个基本的配方ID,

并将其映射到 – >主要配方ID,废弃配料和新配料,如果你想解释这个解决scheme只是问