Firebase:如何以事务方式更新多个节点?(Firebase: How to update multiple nodes transactionally? Swift 3)

编程入门 行业动态 更新时间:2024-10-27 20:30:20
Firebase:如何以事务方式更新多个节点?(Firebase: How to update multiple nodes transactionally? Swift 3)

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

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

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

一次只允许一个清洁工修改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....完成处理程序{(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 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

I am developing an application designed for cleaning services. In this application the employees (cleaners) can read a list of jobs (bookings) which have been made by multiple customers (Users).

All cleaners can read all bookings in Users node. Initially, when a booking is saved in the database by a user, the key claimed: has a value of “false”,meaning it has not been claimed by a cleaner.

Whenever a cleaner wants to claim a job present in the list, he will have to touch a button which will make a request to Firebase Database to modify the value of key claimed to true at path /Users/UID/bookings/bookingNumber

Only one cleaner at a time should be allowed to modify the value of claimed key. If multiple cleaners were allowed to modify the value of claimed key, other cleaners would end up claiming the same job. We don't want that to happen.

Furthermore, after a cleaner modifies the value of claimed key to true, we will need to make another request to path CLeaners/UID/bookings/bookingNumber in order to save the booking he has just claimed in the cleaners node. - According to the firebase docs, we use transactions whenever we want a resource to be modified by only one request at a time, if there are multiple concurrent requests trying to write to the same resource, one of them will succeed. But the problem with using transactions is that it enables writing to only one path, it does not enable writing to multiple paths.

How can I ensure that even though multiple users can read this path /Users/UID/bookings/bookingNumber, only one user at a time can update it? And if the write is successful, further write to the second path Cleaners/UID/bookings/bookingNumber.

We need to take into account that the client's internet connection can drop, the user can quit the app, or simply the phone will switch off unexpectedly any time in-between writing to the paths specified above.

The database structure is as follows

Root Cleaners UID bookings bookingNumber amount: “10” claimed: “true” Users UID otherID bookingNumber amount: “10” claimed: “true” bookingNumber amount: “50” claimed: “false”

To avoid any overwrites, I have decided to use Firebase transactions. I can write to a single node as transaction, but writing to the second node in the completion handler is not a solution since the cleaner's internet connection may drop or app could be quit before a response is received from the server, thus the code in the completion handler {(error, committed,snapshot) in....would not be evaluated and the second write would not succeed.

Another scenario would be: first write is executed, 1. response is received in client app 2. response is not yet received in client app and the user quits the app immediately. In this case the second write will never be executed since no code is yet evaluated after response is received (or not) in the completion handler, thus leaving my database in an inconsistent state.


From Firebase docs:

Transactions are not persisted across app restarts

Even with persistence enabled, transactions are not persisted across app restarts. So you cannot rely on transactions done offline being committed to your Firebase Realtime Database.

Is it possible to write to multiple nodes in a Firebase Database using Firebase Transactions in Swift?

If so, how can I do this? I see no example in this blog from google https://firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html . I do understand that you can write atomically to multiple nodes, but I'd like to write as transaction. I am trying to write to two nodes in the else clause, but I get a warning on this line let updated = updateInUsersAndCleaners as? FIRMutableData

Cast from '[FIRDatabaseReference : FIRMutableData]' to unrelated type 'FIRMutableData' always fails

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

最满意答案

是否可以使用Swift中的Firebase事务写入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事务的问题。

In the Firebase documentation in the section Enable Offline Capabilities it is specified that:

Transactions are not persisted across app restarts Even with persistence enabled, transactions are not persisted across app restarts. So you cannot rely on transactions done offline being committed to your Firebase Realtime Database.

Therefore: 1. there is no way to use firebase transactions at client side to update a value at two or more paths. 2. using a completion callback to perform the second write is not viable since the client could restart the app before a response is received in the completion handler from the firebase server, thus leaving the database in inconsistent state.

I assume that my only option to update data transactionally at the first path and further update the second path with that data that was already written at the first path in Firebase Database, would be to use Conditional Requests over REST as specified in the Firebase Documentation. This would achieve the same functionality provided by Firebase framework for IOS clients.

The client will make a request via Alamofire to my server (I will use Vapor framework so as to leverage the Swift language), once the request is received at the Vapor server, a GET request will be sent to the Firebase Database server root/users/bookings/4875383 in which I will request an ETAG_VALUE

What is ETAG Value?: ( a unique identifier which will be different every time data changes at the path where GET Request is made. Namely if another user writes to the same path before my write request the resource succeeds, my write operation will be rejected since the ETAG value at the path will have already been modified by the other user's write operation. This would enable users to write data transactionally to a path)

a response is received from the Firebase Server containing a an ETAG_VALUE. make a PUT request to the Firebase Server and in the header specify the ETag: [ETAG_VALUE] received from the previous GET request. If the ETAG value posted to the server matches the value at the Firebase Server, the write will succeed. If the location no longer matches the ETag, which might occur if another user wrote a new value to the database, the request fails without writing to the location. The return response includes the new value and ETag. Furthermore, now we can update the value at root/Cleaners/bookings/4875383 to reflect the job that was claimed by a cleaner.

更多推荐

本文发布于:2023-07-07 09:09:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1061437.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:多个   节点   事务   方式   Firebase

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!