gpt4 book ai didi

database-design - Datomic:使用 'reset' 操作的一对多关系的架构

转载 作者:行者123 更新时间:2023-12-03 03:00:48 24 4
gpt4 key购买 nike

我正在寻找有关在 Datomic 中建模某些对多关系的方法的反馈。

问题

假设我想为一个域设计 Datomic 模式,其中一个人有一个最喜欢的电影列表。例如,John最喜欢的电影是 Gladiator , Star Wars ,和Fight Club .

在 Datomic 中对此进行建模的最明显的模式是使用基数多属性,例如:

#{["John" :person/favorite-movies "Gladiator"]
["John" :person/favorite-movies "Star Wars"]
["John" :person/favorite-movies "Fight Club"]}

这种方法可以轻松地从列表中添加或删除电影(只需使用 :db/add:db/retract ),但我发现它对于重置整个电影列表来说是不切实际的 - 您本质上是需要计算旧列表和新列表之间的差异,并且必须在事务函数中运行。当列表的元素不是标量时,情况会变得更糟。

替代方法

作为一种替代方法,我正在考虑使用集合实体引入间接:

#{["John" :person/favorite-movies 42]
[42 :set.string/contains "Gladiator"]
[42 :set.string/contains "Star Wars"]
[42 :set.string/contains "Fight Club"]}

通过这种方法,:person/favorite-movies是一个基数为一、引用类型的属性,并且 :set.string/contains是基数多、字符串类型的属性。重置列表只需创建一个新的设置实体即可:

[{:db/id "John"
:person/favorite-movies {:db/id (d/tempid :db.part/user)
:set.string/contains ["Gladiator"
"The Lord of the Rings"
"A Clockwork Orange"
"True Romance"]}}]

这种对多关系建模的方法是否存在已知的局限性?

<小时/>

编辑:一个不那么简单的用例

在关系是引用类型而不是标量类型的情况下研究这个问题更有意义,因为 Datomic 中的引用类型属性会出现一些问题。

研究一个用例也更有意义,在该用例中,关系的“重置”操作更有意义,而“最喜欢的电影”的情况并非如此。

示例:带有复选框的表单,用户可以在其中提供 AnswerQuestion通过选择一组Option s。用户可以更新她的AnswerQuestion 。目标是对 Answer - Option 进行建模关系。

此信息模型的规范 Datomic 模式是:

  • :answer/id :答案的唯一 ID(标量类型,唯一标识)
  • :option/id :选项的唯一ID(标量类型,唯一标识)
  • :answer/selectedOptions (引用类型,基数多)

最佳答案

  • 这种技术更为复杂:您需要管理两个实体而不是一个实体。
  • 如果您使用通用 attr 来保存集合成员(示例中的 :set.string/contains),您将不再拥有 favorite-movies 值的有用索引)。要获取有用的索引,您需要一对属性:例如 :person/favorite-movies:person.favorite-movies/items
  • 对用户喜爱的电影的更改历史记录的重建起来更加复杂。您现在可以不再简单地查看 :person/favorite-movies,您需要随时知道它指向哪个集合实体,并查看集合实体的历史记录。
  • 您的应用程序需要区分“我正在重置集合”与“我正在更改集合并希望合并更改”。应用程序模型实际上可能不存在任何此类区别。
  • 您最终可能会得到孤立的“集合”实体,其中包含未引用的数据。例如:同时,一个对等点发送重置(即断言新的集合实体),而另一个对等点向现有集合添加一个项目。如果第二个节点的交易发生在第一个节点之后,那么你现在就有了一个孤立的数据原子。

最好的解决方案是进行细粒度的更改。例如,如果用户从集合中添加或删除特定项目,则每个添加或删除都应该是一个只有该断言或撤回的事务。集合操作是可交换的,因此两个用户攻击同一个集合不会造成任何伤害。 (除非您有派生数据,在这种情况下竞争条件很重要。)

如果您确实需要“重置集合,使其看起来像这样”操作,更好的解决方案是使用事务函数,该函数接收您想要的整个集合值并计算获取当前值所需的添加和收回成为你想要的新值。这是一个可以执行此操作的 tx 函数:

{:db/ident :db.fn/resetAttribute
:db/doc "Unconditionally set an entity's attribute's values to those provided,
retracting all other existing values.

Values must be a collection (list, seq, vector), even for cardinality-one
attributes. An empty collection (or nil) will retract all values. The values
themselves must be primitive, i.e. no map forms are permitted for refs, use
tempids directly. If the attribute is-component, removed values will be
:db.fn/retractEntity-ed."
:db/fn
#db/fn {:lang "clojure"
:params [db ent attr values]
:code (let [eid (datomic.api/entid db ent)
aid (datomic.api/entid db attr)
{:keys [value-type is-component]} (datomic.api/attribute db aid)
newvalues (if (= value-type :db.type/ref)
(into #{} (map #(if (string? %) % (d/entid db %))) values)
(into #{} values))
oldvalues (into #{} (map :v) (datomic.api/datoms db :eavt eid aid))]
(-> []
(into (comp
(remove newvalues)
(map (if is-component
#(do [:db.fn/retractEntity %])
#(do [:db/retract eid aid %]))))
oldvalues)
(into (comp
(remove oldvalues)
(map #(do [:db/add eid aid %])))


newvalues)))}}

你可以像这样使用它:

[:db.fn/resetAttribute [:person/id "John"] :person/favorite-movies
["Gladiator" "The Lord of the Rings" "A Clockwork Orange" "True Romance"]]]

;; Or to retract *all* existing values:
[:db.fn/resetAttribute [:person/id "John"] :person/favorite-movies nil]

关于database-design - Datomic:使用 'reset' 操作的一对多关系的架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42112557/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com