Firebase:如何以事务方式更新多个节点? Swift 3 [英] Firebase: How to update multiple nodes transactionally? Swift 3
问题描述
所有清洁工都可以读取用户节点中的所有预订。最初,当用户将预订保存在数据库中时, 每当清洁工想要求职时,他都可以在列表中看到,他必须触摸一个按钮,该按钮会向Firebase数据库发出请求,以修改在 每次只允许一个清理者修改 此外,清理程序将 如何确保即使多个用户可以读取此路径 我们需要考虑到客户的互联网连接可能会丢失,用户可以退出应用程序,或者只是在写入上面指定的路径之间的任何时候,电话会意外关闭。 数据库结构如下: 为了避免重写,我决定使用Firebase交易。我可以作为事务写入单个节点,但是写入完成处理程序中的第二个节点并不是一个解决方案,因为清理程序的Internet连接可能会丢失,或者在从服务器收到响应之前可能会退出应用程序,因此,完成处理程序 另一种情况是:首先写入被执行, 从Firebase文档: 是否可以使用Swift中的Firebase事务写入Firebase数据库中的多个节点?如果是这样,我该怎么做呢?我从Google https:/ /firebase.googleblog.com/2015/09/introducing-multi-location-updates-and_86.html 。我明白,你可以原子写入多个节点,但我想写作交易。 从'[FIRDatabaseReference:FIRMutableData]'转换为不相关的类型 在Firebase文档 启用离线功能 指定: 应用程序重新启动时不会持久化事务 因此: 我假设我的唯一选项可以在第一个路径上事务性地更新数据,并使用Firebase数据库中第一个路径中已写入的数据进一步更新第二个路径,即可使用 Firebase文档中指定的对REST的条件请求。键声明:的值为
false
,这意味着它没有被清洁工所要求。
/ Users / UID / bookings / bookingNumber $ c 到
真 $ c
$ b 声明的值
键。如果允许多个清洁工修改所声称的键的值,其他清洁工最终会声称做同样的工作。我们不希望这样的事情发生。
true
,我们将需要再次请求路径 CLeaners / UID / bookings / bookingNumber
,以便保存他在清洁工节点声明的预订。
- 根据firebase文档,我们使用事务,只要我们希望一次只有一个请求修改资源,如果有多个并发请求试图写入相同的资源,其中一个成功。
但是使用事务的问题是它只能写入一个路径,不能写到多个路径。
/ Users / UID / bookings / bookingNumber
,一次只能有一个用户更新它?如果写入成功,则进一步写入第二条路径 Cleaners / UID / bookings / bookingNumber
。
$ root
清理
UID
预订
bookingNumber
amount:10
声明:true
用户
UID
otherID
bookingNumber
数量:10
声明:true
bookingNumber
数量:50
声明:false
{(error,committed,snapshot)in ....
不会被评估,第二次写入不会成功。
1.客户端应用程序接收到响应
2.响应尚未在客户端应用程序
中收到,用户立即退出应用程序。在这种情况下,第二次写入将永远不会执行,因为在完成处理程序中收到响应(或不响应)之后还没有对代码进行评估,从而使数据库处于不一致的状态。
<即使启用了持久性,事务也不会持续在
应用程序重新启动之间持续存在。因此,您不能依赖于离线执行的交易,将
委托给您的Firebase实时数据库。
我正在尝试写入 else
子句中的两个节点,但是我在这行上得到一个警告: let updated = updateInUsersAndCleaners as? FIRMutableData
'FIRMutableData'失败
类ClaimDetail:UIViewController,UITableViewDelegate,UITableViewDataSource {
var valueRetrieved = [String:AnyObject]()
var uid:String?
@IBAction func claimJob(_ sender:Any){
dbRef.runTransactionBlock({(_ currentData:FIRMutableData) - > FIRTransactionResult in
//如果valueRetrieved是nil abort
guard let val = currentData.value as?[String:AnyObject] else {
return FIRTransactionResult.abort()
}
self.valueRetrieved = val
$ b guard let uid = FIRAuth.auth()?currentUser?.uid else {
return FIRTransactionResult.abort()
}
self.uid = uid
for self.valueRetrieved.keys {
print(key is \(key))
//展开'Claimed' key
guard let keyValue = self.valueRetrieved [Claimed] as String else {
return FIRTransactionResult.abort()
}
//检查key值为真
if keyValue ==true{
//预定已经分配,中止
返回FIRTransactionResult.abort()
} else {
//将新值写入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)
//为两个节点创建要更新的数据
let updateInUsersAndCleaners = [usersRef:currentData,cleanersRef:currentData]
let updated = updateInUsersAndCleaners as? FIRMutableData
返回FIRTransactionResult.success(withValue:updated!)
} // end of else
} //结束for self中的键
返回FIRTransactionResult.abort()
)){(错误,提交,快照)在
如果让错误=错误{
//显示警告与错误,询问用户再试一次
self.alertText =预订无法获取,请重试。
self.alertActionTitle =OK
self.segueIdentifier =unwindfromClaimDetailToClaim
self.showAlert()
} else if committed == true {
self.alertText =预订声明。请检查您的日历
self.alertActionTitle =OK
self.segueIdentifier =unwindfromClaimDetailToClaim
self.showAlert()
// end of claimJob按钮
} //课程结束
扩展ClaimDetail {
//显示警告给用户,然后继续声明tableView
func showAlert(){
let alertMessage = UIAlertController(title:,message:self.alertText在
中使用UIAlertAction(title:self.alertActionTitle,style:.default,handler:{(action:UIAlertAction))self.performSegue(withIdentifier:self.segueIdentifier,发件人:自己)
}))
self.present(alertMessage,animated:true,completion:nil)
}
//从完成处理函数新数据
func createDictionary() - > AnyObject {
let timeStamp = Int(Date()。timeIntervalSince1970)
self.valueRetrieved [CleanerUID] = uid as AnyObject?
self.valueRetrieved [TimeStampBookingClaimed] = timeStamp as AnyObject?
self.valueRetrieved [Claimed] =true作为AnyObject?
print(line 89 extension CLaim Detail)
return self.valueRetrieved as AnyObject
}
} //延期结束ClaimDetail
< blockquote>
即使启用了持久性,事务也不会持续存在于
应用程序重新启动期间。
因此,您不能依赖于离线完成的交易,将
交给您的Firebase实时数据库。
1 。在客户端无法使用firebase事务来更新两个或更多路径的值。
2.使用完成回调执行第二次写入不是vi因为客户端可以在firebase服务器的完成处理程序收到响应之前重新启动应用程序,从而使数据库处于不一致状态。
这将实现IOS的Firebase框架提供的相同功能客户端。
- 客户端将通过Alamofire向我的服务器发送请求(我将使用Vapor框架,以利用Swift语言),一旦Vapor服务器收到请求,GET请求将被发送到Firebase数据库服务器
root / users / bookings / 4875383
在其中h我将申请 ETAG_VALUE
什么是ETAG值?(唯一的标识符,每次数据在GET请求的路径上发生变化时都会有所不同。也就是说,如果另一个用户在我的写入请求之前写入相同的路径,资源成功,我的写入操作将被拒绝,因为路径上的ETAG值已被其他用户的写入操作修改。这将使用户能够以事务方式将数据写入路径)
-
- 从包含ETAG_VALUE的Firebase服务器收到响应。
-
- 向Firebase服务器发送PUT请求,指定从前一个GET请求收到的 ETag:[ETAG_VALUE] 。如果发布到服务器的ETAG值与Firebase服务器上的值相匹配,写入操作将会成功。如果位置不再与ETag相匹配(如果另一个用户向数据库写入新值,则可能会发生该错误),请求将失败,而不写入该位置。返回响应包括新值和ETag。
-
- 此外,现在我们可以更新
root / Cleaners / bookings / 4875383
来反映清洁工所声称的工作。
- 此外,现在我们可以更新
- 向Firebase服务器发送PUT请求,指定从前一个GET请求收到的 ETag:[ETAG_VALUE] 。如果发布到服务器的ETAG值与Firebase服务器上的值相匹配,写入操作将会成功。如果位置不再与ETag相匹配(如果另一个用户向数据库写入新值,则可能会发生该错误),请求将失败,而不写入该位置。返回响应包括新值和ETag。
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 he can see 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 resources 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 insure 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
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
- 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
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.
- Furthermore, now we can update the value at
这篇关于Firebase:如何以事务方式更新多个节点? Swift 3的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!