Firebase:如何以事务方式更新多个节点?斯威夫特3

时间:2017-08-04 13:22:24

标签: swift firebase firebase-realtime-database

我正在开发一个专为清洁服务而设计的应用程序。在此应用程序中,员工(清洁工)可以读取由多个客户(用户)制作的工作(预订)列表。

所有清洁工都可以阅读“用户”节点中的所有预订。最初,当用户将预订保存在数据库中时,密钥claimed:的值为“false”,这意味着清洁工没有声明它。

每当清洁工想要申请列表中的工作时,他都必须触摸一个按钮,该按钮会向Firebase数据库发出请求,以便在路径中将密钥claimed的值修改为true /Users/UID/bookings/bookingNumber

一次只允许一个清洁工修改claimed密钥的值。如果允许多个清洁工修改claimed密钥的值,其他清洁工最终会声称同样的工作。我们不希望这种情况发生。

此外,在清理工具将claimed密钥的值修改为true后,我们需要向路径CLeaners/UID/bookings/bookingNumber发出另一个请求,以便保存他刚刚声明的预订清洁工节点      - 根据firebase文档,每当我们希望一次只有一个请求修改资源时,如果有多个并发请求尝试写入同一资源,我们就会使用事务,其中一个会成功。 但是使用事务的问题在于它可以写入只有一条路径,它不能将写入多条路径

即使多个用户可以读取此路径/Users/UID/bookings/bookingNumber,我怎样才能确保一次只有一个用户可以更新它?如果写入成功,则进一步写入第二条路径Cleaners/UID/bookings/bookingNumber

我们需要考虑到客户端的互联网连接可能会丢失,用户可以退出应用程序,或者只是在写入上面指定的路径之间的任何时间,手机都会意外关闭。

数据库结构如下

Root
  Cleaners
    UID
     bookings
       bookingNumber
         amount: “10”
         claimed: “true”


   Users
     UID
      otherID
        bookingNumber
          amount: “10”
          claimed: “true”

         bookingNumber
          amount: “50”
          claimed: “false”

为避免任何覆盖,我决定使用Firebase交易。我可以将单个节点写为事务,但是在完成处理程序中写入第二个节点不是解决方案,因为在从服务器收到响应之前,清理器的Internet连接可能会丢失或应用程序可能会退出,因此代码位于完成处理程序{(error, committed,snapshot) in....将不会被评估,第二次写入将不会成功。

另一种情况是:执行第一次写入,
 1.在客户端应用程序中收到响应
 2.客户端应用程序尚未收到回复      并且用户立即退出应用程序。在这种情况下,第二次写入将永远不会执行,因为在完成处理程序中收到(或不响应)响应之后尚未评估代码,从而使我的数据库处于不一致状态。

来自Firebase文档:

  

跨应用重新启动不会保留交易

     

即使启用了持久性,交易也不会持续存在   应用重启。所以你不能依赖离线完成的交易   致力于您的Firebase实时数据库。

  • 是否可以使用Swift中的Firebase事务写入Firebase数据库中的多个节点?

如果是这样,我该怎么做?我在google https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html的博客中看不到任何示例。我知道你可以原子地写入多个节点,但我想写成事务。 我正在尝试写入else子句中的两个节点,但我在此行let updated = updateInUsersAndCleaners as? FIRMutableData

上收到警告
  

从'[FIRDatabaseReference:FIRMutableData]'转换为不相关的类型   'FIRMutableData'总是失败

    class ClaimDetail: UIViewController,UITableViewDelegate,UITableViewDataSource {

 var valueRetrieved = [String:AnyObject]()
 var uid:String?

   @IBAction func claimJob(_ sender: Any) {

      dbRef.runTransactionBlock({ (_ currentData:FIRMutableData) -> FIRTransactionResult in

//if valueRetrieved is nil abort
  guard let val = currentData.value as? [String : AnyObject] else {
    return FIRTransactionResult.abort()
    }
          self.valueRetrieved = val

  guard let uid = FIRAuth.auth()?.currentUser?.uid else {
         return FIRTransactionResult.abort()
        }
              self.uid = uid

    for key in self.valueRetrieved.keys {
         print("key is \(key)")

   //unwrap value of 'Claimed' key
  guard let keyValue = self.valueRetrieved["Claimed"] as? String else {
              return FIRTransactionResult.abort()
        }

            //check if key value is true
               if keyValue == "true"{

                //booking already assigned, abort
                  return FIRTransactionResult.abort()

            } else {
              //write the new values to firebase
               let newData =  self.createDictionary()
                  currentData.value = newData

             let usersRef = self.dbRef.child("Users").child(FullData.finalFirebaseUserID).child(FullData.finalStripeCustomerID).child(FullData.finalBookingNumber)
            let cleanersRef = self.dbRef.child("Cleaners").child(self.uid!).child("bookings").child(FullData.finalBookingNumber)

  //Create data we want to update for both nodes
    let updateInUsersAndCleaners = [usersRef:currentData,cleanersRef:currentData]
                let updated = updateInUsersAndCleaners as? FIRMutableData
                  return FIRTransactionResult.success(withValue: updated!)

      }//end of else
}//end of for key in self

          return FIRTransactionResult.abort()
    }) {(error, committed,snapshot) in

        if let error = error {
            //display an alert with the error, ask user to try again

         self.alertText = "Booking could not be claimed, please try again."
             self.alertActionTitle = "OK"
               self.segueIdentifier = "unwindfromClaimDetailToClaim"
                  self.showAlert()

        } else if committed == true {

        self.alertText = "Booking claimed.Please check your calendar"
            self.alertActionTitle = "OK"
            self.segueIdentifier = "unwindfromClaimDetailToClaim"
               self.showAlert()
        }
    }

 }//end of claimJob button

}//end of class


  extension ClaimDetail {

//show alert to user and segue to Claim tableView
   func showAlert() {
      let alertMessage = UIAlertController(title: "", message: self.alertText, preferredStyle: .alert)
     alertMessage.addAction(UIAlertAction(title: self.alertActionTitle, style: .default, handler: { (action:UIAlertAction) in
          self.performSegue(withIdentifier: self.segueIdentifier, sender: self)
    }))
         self.present(alertMessage, animated: true,completion: nil)
 }


    //create dictionary with data received from completion handler and the new data
  func createDictionary() -> AnyObject {
     let timeStamp = Int(Date().timeIntervalSince1970)
       self.valueRetrieved["CleanerUID"] = uid as AnyObject?
         self.valueRetrieved["TimeStampBookingClaimed"] = timeStamp as AnyObject?
         self.valueRetrieved["Claimed"] = "true" as AnyObject?
          print("line 89 extension CLaim Detail")
            return self.valueRetrieved as AnyObject
      }
   } // end of extension ClaimDetail

2 个答案:

答案 0 :(得分:4)

  

是否可以使用在Firebase数据库中写入多个节点   Swift中的Firebase事务?

用例并不十分清楚为什么需要事务 - 似乎需要同时将数据写入多个节点。如果是这种情况,您可以在没有交易的情况下同时写入多个节点。

这是一个例子。给定一个结构

messages
  msg_0
    msg: "some message"
  msg_1
    msg: "another message"
  msg_2
    msg: "cool  message"

运行以下代码

let messagesRef = self.ref.child("messages")
let path0 = "msg_0/msg"
let path1 = "msg_1/msg"
let path2 = "msg_2/msg"

let childUpdates = [
    path0: "0 message",
    path1: "1 message",
    path2: "2 message"
]

messagesRef.updateChildValues(childUpdates)

结果是同时写:

messages
  msg_0
    msg: "0 message"
  msg_1
    msg: "1 message"
  msg_2
    msg: "2 message"

如果这不能提供解决方案,我们可能需要更清楚地了解有关使用Firebase交易的问题。

答案 1 :(得分:0)

Enable Offline Capabilities部分的Firebase文档中,指定了:

  

应用重启后,交易不会持续存在   即使启用了持久性,交易也不会持久化   app重启。
  所以你不能依赖离线完成的交易   致力于您的Firebase实时数据库。

因此:
1.无法在客户端使用firebase事务来更新两个或多个路径的值 2.使用完成回调来执行第二次写入是不可行的,因为客户端可以在来自firebase服务器的完成处理程序中收到响应之前重新启动应用程序,从而使数据库处于不一致状态。

我认为我唯一的选择是在第一条路径上以事务方式更新数据,并使用已在Firebase数据库的第一条路径上写入的数据进一步更新第二条路径,这将是使用Conditional Requests over REST中指定的Firebase文档。
这将实现Firebase框架为IOS客户端提供的相同功能。

    1. 客户端将通过Alamofire向我的服务器发出请求(我将使用Vapor框架以利用Swift语言),一旦Vapor服务器收到请求,GET请求将被发送到Firebase数据库服务器root/users/bookings/4875383我将在其中请求 ETAG_VALUE

什么是ETAG值?: (一个唯一的标识符,每次数据在GET请求的路径发生变化时都会有所不同。即使是另一个用户在写入请求之前写入相同的路径资源成功,我的写入操作将被拒绝,因为路径上的ETAG值已经被其他用户的写入操作修改过。这将使用户能够以事务方式将数据写入路径)

    1. 从Firebase服务器收到包含ETAG_VALUE的响应。
    1. 向Firebase服务器发出PUT请求,并在标头中指定从上一个GET请求收到的 ETag:[ETAG_VALUE] 。如果发布到服务器的ETAG值与Firebase服务器上的值匹配,则写入将成功。如果该位置不再与ETag匹配(如果另一个用户向数据库写入新值,则可能发生该位置),请求将失败而不写入该位置。返回响应包括新值和ETag。
    1. 此外,现在我们可以更新root/Cleaners/bookings/4875383的值,以反映清洁工声称的工作。