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

问题描述 投票:2回答:3

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

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

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

所以,我当然意味着这可能是一个云功能。

显然,这个操作必须是原子的,你必须防止赛道效应等等。

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

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

所有原子,带锁。

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

  • 已经有了令人敬畏的ref.transaction系统,但我不认为它在这里是相关的。
  • 也许以变态的方式使用触发器?

IDK


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

enter image description here

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

firebase firebase-realtime-database
3个回答
4
投票

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

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

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

这样做基本上是使用客户端代码和(一些公认的棘手的)安全规则重新实现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


0
投票

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

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

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

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


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
© www.soinside.com 2019 - 2024. All rights reserved.