2017-08-04 70 views
1

我正在開發一個專門用於清潔服務的應用程序。在這個應用程序中,員工(清潔工)可以閱讀由多個客戶(用戶)完成的工作清單(預訂)。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

回答

0

在部分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值已被其他用戶的寫入操作修改過。這將使得用戶能夠事務數據寫入路徑)

  • 由含有一個ETAG_VALUE的火力地堡服務器接收的響應。
  • 使PUT請求到火力地堡服務器和在標題指定ETag的:[ETAG_VALUE]從先前的GET請求接收。如果發佈到服務器的ETAG值與Firebase服務器上的值匹配,寫入操作將會成功。如果該位置不再與ETag匹配(如果另一個用戶向數據庫寫入新值,則可能會發生該錯誤),則該請求會失敗,而不寫入該位置。返回響應包括新值和ETag。
  • 此外,現在我們可以更新在root/Cleaners/bookings/4875383值以反映,是由一個清潔器要求的作業。
1

是否有可能寫入多個節點使用 火力地堡交易斯威夫特一個火力地堡數據庫?

用例並不是非常清楚爲什麼需要事務 - 看起來數據需要同時寫入多個節點。如果是這種情況,您可以在沒有事務的情況下同時寫入多個節點。

下面是一個例子。給定一個結構

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://stackoverflow.com/questions/46100421。非常感謝 – bibscy

相關問題