我正在开发一个专为清洁服务而设计的应用程序。在此应用程序中,员工(清洁工)可以读取由多个客户(用户)制作的工作(预订)列表。
所有清洁工都可以阅读“用户”节点中的所有预订。最初,当用户将预订保存在数据库中时,密钥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
答案 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客户端提供的相同功能。
root/users/bookings/4875383
我将在其中请求 ETAG_VALUE 什么是ETAG值?: (一个唯一的标识符,每次数据在GET请求的路径发生变化时都会有所不同。即使是另一个用户在写入请求之前写入相同的路径资源成功,我的写入操作将被拒绝,因为路径上的ETAG值已经被其他用户的写入操作修改过。这将使用户能够以事务方式将数据写入路径)
root/Cleaners/bookings/4875383
的值,以反映清洁工声称的工作。