Firebase RTD,原子“移动”......从两个“表”中删除并添加?

时间:2018-02-28 15:15:24

标签: firebase firebase-realtime-database

在Firebase实时数据库中,这是一个非常常见的事务性事件

  • “table”A - 将其视为“待定”
  • “table”B - 将其视为“结果”

某些状态发生,您需要将项目从A“移动”到B。

所以,我当然认为这可能是一个云功能。

显然,此操作必须是原子,并且必须防止赛道效应等等。

因此,对于项目123456,您必须做三件事

  • 阅读A / 123456 /
  • 删除A / 123456 /
  • 将值写入B / 123456

所有原子,带锁。

简而言之,Firebase实现这一目标的方式是什么?

  • 已经有了很棒的ref.transaction系统,但我不认为它与此相关。

  • 也许以变态的方式使用触发器?

IDK

对于在网上搜索的人来说,值得注意的是令人难以置信的新Firestore(很难想象有什么东西比传统的Firebase更令人难以置信,但是你有它......),新的Firestore系统内置 .......

enter image description here

这个问题是关于旧的传统Firebase Realtime。

3 个答案:

答案 0 :(得分:4)

Gustavo的回答允许使用单个API调用进行更新,该调用可以完成成功或失败。而且由于它不必使用事务,因此争用问题要少得多。它只是加载它想要移动的键的值,然后写一个更新。

问题是有人可能在此期间修改了数据。因此,您需要使用安全规则来捕获这种情况并拒绝它。所以食谱变成了:

  1. 读取源节点的值
  2. 将值写入新位置,同时在单个update()调用中删除旧位置
  3. 安全规则验证操作,接受或拒绝它
  4. 如果被拒绝,则客户从#1
  5. 重试

    这样做本质上是使用客户端代码和(有些公认的棘手的)安全规则重新实现Firebase数据库事务。

    为了能够做到这一点,更新变得有点棘手。假设我们有这种结构:

    "key1": "value1",
    "key2": "value2"
    

    我们希望将value1key1移至key3,然后Gustavo的方法会发送此JSON:

    ref.update({
      "key1": null,
      "key3": "value1"
    })
    

    何时可以使用以下规则轻松验证此操作:

    ".validate": "
        !data.child("key3").exists() && 
        !newData.child("key1").exists() &&
        newData.child("key3").val() === data.child("key1").val()
    "
    

    用语言说:

    • key3目前没有值。
    • 更新后key1没有值
    • key3的新值是key1
    • 的当前值

    这很有效,但遗憾的是我们在规则中对key1key3进行了硬编码。为了防止对它们进行硬编码,我们可以将密钥添加到更新语句中:

    ref.update({
      _fromKey: "key1",
      _toKey: "key3",
      key1: null,
      key3: "value1"
    })
    

    不同之处在于我们添加了两个具有已知名称的键,以指示移动的源和目的地。现在有了这个结构,我们可以获得所需的所有信息,我们可以通过以下方式验证移动:

    ".validate": "
        !data.child(newData.child('_toKey').val()).exists() && 
        !newData.child(newData.child('_fromKey').val()).exists() &&
        newData.child(newData.child('_toKey').val()).val() === data.child(newData.child('_fromKey').val()).val()
    "
    

    阅读时间稍长,但每行仍然与以前相同。

    在我们的客户代码中:

    function move(from, to) {
      ref.child(from).once("value").then(function(snapshot) {
        var value = snapshot.val();
        updates = {
          _fromKey: from,
          _toKey: to
        };
        updates[from] = null;
        updates[to] = value;
        ref.update(updates).catch(function() {
          // the update failed, wait half a second and try again
          setTimeout(function() {
            move(from, to);
          }, 500);
        });
    }
    move ("key1", "key3");
    

    如果您想使用这些规则的代码,请查看:https://jsbin.com/munosih/edit?js,console

答案 1 :(得分:0)

Firebase可与字典,a.k.a,键值对配合使用。要在同一个事务中更改多个表中的数据,您可以获得基本引用,并使用包含“所有指令”的字典,例如在Swift中:

let reference = Database.database().reference() // base reference

let tableADict = ["TableA/SomeID" : NSNull()] // value that will be deleted on table A
let tableBDict = ["TableB/SomeID" : true] // value that will be appended on table B, instead of true you can put another dictionary, containing your values

然后您应该合并(如何在此处执行:How do you add a Dictionary of items into another Dictionary)将两个词典合并为一个,我们称之为finalDict, 然后你可以更新这些值,并且两个表都将被更新,从A中删除并“移动到”B

reference.updateChildValues(finalDict) // update everything on the same time with only one transaction, w/o having to wait for one callback to update another table

答案 2 :(得分:0)

实时数据库中没有“表”,因此我将使用术语“位置”来引用包含一些子节点的路径。

实时数据库无法在两个不同的位置进行原子事务处理。当您执行交易时,您必须选择一个位置,并且您只能在该单个位置进行更改。

您可能认为您只能在数据库的根目录进行交易。这是可能的,但是面对数据库中任何地方的并发非事务写入操作,这些事务可能会失败。要求在事务发生的位置任何地方都不得进行非事务性写入。换句话说,如果您想在某个地点进行交易,那么所有客户都必须在那里进行交易,没有客户可以在没有交易的情况下在那里进行交易。

如果您在数据库的根目录进行交易,那么这条规则肯定会有问题,客户端可能在没有事务的情况下在整个地方编写数据。因此,如果您想执行原子“移动”,您必须让所有您的客户端始终在移动的公共根位置使用事务,或者接受您不能以原子方式做到这一点。