Firebase:如何以事务方式更新多个节点? Swift 3

问题描述:

我正在开发一个专门用于清洁服务的应用程序。在这个应用程序中,员工(清洁工)可以阅读由多个客户(用户)完成的工作清单(预订)。Firebase:如何以事务方式更新多个节点? Swift 3

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

每当清洁想要求一个工作,他可以​​在列表中看到,他将不得不轻触按钮,这将使到火力地堡数据库的请求,在路径的关键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” 

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

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


从火力地堡文档:

交易不能跨应用程序重新启动持续

即使启用持久性,事务不能跨 应用程序重新启动依然存在。因此,您无法依靠脱机完成的事务被提交到您的Firebase实时数据库 。

  • 是否有可能写入多个节点使用火力地堡交易斯威夫特一个火力地堡数据库?

如果是这样,我该怎么做?我从谷歌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 
+0

该博客文章显示了Objective C中的一个示例。您是否难以将其转换为Swift?如果是这样:更新您的问题,以显示你已经尝试过。 –

+0

@FrankvanPuffelen我没有看到使用此函数'runTransactionBlock'的任何示例,如https://firebase.google.com/docs/database/ios/read-and-write#save_data_as_transactions中所述。我应该只是创建两个引用到firebase数据库中的不同节点,然后将数据写入事务?我在博客中找不到任何示例,显示使用Firebase交易将数据写入两条不同的路径。 – bibscy

+0

@FrankvanPuffelen请参阅我更新的问题 – bibscy

在部分Enable Offline Capabilities的火力地堡文档中,规定:

交易不会在应用程序重新启动时持续存在
即使启用了持久性,交易也不会在 应用程序重新启动时持续存在。
因此,您无法依靠脱机完成的事务处理为 ,该事务已落实到您的Firebase实时数据库中。

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

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

    1. 客户端将通过Alamofire请求到我的服务器(我将使用蒸汽框架,以便充分利用雨燕语言),一旦请求中,蒸气服务器接收,GET请求会发送到火力地堡数据库服务器root/users/bookings/4875383中,我会请求ETAG_VALUE

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

    1. 开始=“2”>
    2. 由含有一个ETAG_VALUE的火力地堡服务器接收的响应。
      1. 开始=>
      2. 使PUT请求到火力地堡服务器和在标题指定ETag的:[ETAG_VALUE]从先前的GET请求接收。如果发布到服务器的ETAG值与Firebase服务器上的值匹配,写入操作将会成功。如果该位置不再与ETag匹配(如果另一个用户向数据库写入新值,则可能会发生该错误),则该请求会失败,而不写入该位置。返回响应包括新值和ETag。
        1. 开始=“4”>
        2. 此外,现在我们可以更新在root/Cleaners/bookings/4875383值以反映,是由一个清洁器要求的作业。

        是否有可能写入多个节点使用 火力地堡交易斯威夫特一个火力地堡数据库?

        用例并不是非常清楚为什么需要事务 - 看起来数据需要同时写入多个节点。如果是这种情况,您可以在没有事务的情况下同时写入多个节点。

        下面是一个例子。给定一个结构

        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交易的问题中。

        +0

        我已更新我原来的问题。我不需要同时写入相同的资源。一次只能有一个请求成功。如果100个用户试图在'/ Users/UID/bookingings/bookingNumber'处写入这个资源,则只有一个写请求应该成功,其他99个应该失败。我可以在同一个事务中写入两条不同的路径吗?我想避免使用事务完成处理程序写入第二个路径'Cleaners/UID/bookings/bookingNumber',因为客户端可能会退出应用程序或耗尽电量,而第二次写入将永远不会执行。 – bibscy

        +0

        抱歉是一种痛苦。我认为这个例子类似于我的用例。为了使它与我的情况更相似,您将不得不假设多个客户试图借记“借记支票帐户”并贷记他们的“信用储蓄帐户”。我想知道这是否可以通过Swift中的Firebase IOS SDK客户端来实现。 http://docs.oracle.com/javaee/5/tutorial/doc/bncii.html – bibscy

        +0

        我正在尝试使用您的答案以更新多个路径上的数据,但我无法使其工作。请参阅我的问题https://*.com/questions/46100421。非常感谢 – bibscy