2017-02-28 123 views
1

我的数据结构是类似以下内容:检索从火力地堡数据,而在另一个火力地堡数据库参考使用它

restaurant_owners 
    | 
    |owner_id (a unique ID) 
     | 
     |restaurant_name 
     |email 

restaurant_menus 
    | 
    |restaurant_name 
     | 
     |dish_type (drinks, appetizer, etc...) 
      | 
      |dish_id (a unique ID) 
       | 
       |name 
       | 
       |price 

应用的想法基本上是允许“restaurant_owners”登录和管理的菜单他们各自的餐厅。但是我有下面的代码的问题:(注意fetchDish函数被调用viewDidLoad中)

func fetchDish() { 

    var restaurantName: String? 

    let uid = FIRAuth.auth()?.currentUser?.uid 

    //first time referencing database 
    FIRDatabase.database().reference().child("owners").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in 

     if let dictionary = snapshot.value as? [String: AnyObject] { 

      DispatchQueue.main.async{ 
       restaurantName = dictionary["name"] as? String 
       print(restaurantName!) 
      } 
     } 
    }) 

    //second time referencing database 
    FIRDatabase.database().reference().child("restaurants").child(restaurantName!).child("appetizer").observe(.childAdded, with: { (snapshot) in 

     if let dictionary = snapshot.value as? [String: AnyObject] { 
      let dish = Dish() 
      dish.setValuesForKeys(dictionary) 
      self.dishes.append(dish) 

      DispatchQueue.main.async { 

       self.tableview.reloadData() 
      } 
     } 

    }, withCancel: nil) 
} 

我所试图做的是获取登录的用户的餐厅为当前的名称,将其存储在变量“restaurantName”中。然后,当我第二次引用数据库时,我可以在.child(例如:.child(restaurantName))中使用此变量。

但是,当我运行这个时,我得到一个错误,说restaurantName(在数据库引用中)的值为零。我试着放入一些断点,看起来第二个数据库引用的第一行在第一个数据库引用的“内部”之前被操作,所以基本上restaurantName在任何值被存储之前被调用。

为什么会发生这种情况?我如何解决这个问题?另外,如果我完全错误的做法,最佳做法是什么?

NoSQL对我来说是非常新的,我完全不知道应该如何设计我的数据结构。感谢您的帮助,如果您需要其他信息,请告诉我。

UPDATE:

问题可通过改变我的数据结构是什么周杰伦曾建议解决。下面的代码是对我工作:(修改周杰伦的代码位)

func fetchOwner() { 

    let uid = FIRAuth.auth()?.currentUser?.uid 
    let ownersRef = FIRDatabase.database().reference().child("owners") 
    ownersRef.child(uid!).observeSingleEvent(of: .value, with: { snapshot in 

     if let dict = snapshot.value as? [String: AnyObject] { 
      let restaurantID = dict["restaurantID"] as! String 
      self.fetchRestaurant(restaurantID: restaurantID) 
     } 

    }, withCancel: nil) 
} 

func fetchRestaurant(restaurantID: String) { 

    let restaurantsRef = FIRDatabase.database().reference().child("restaurants") 
    restaurantsRef.child(restaurantID).child("menu").observe(.childAdded, with: { snapshot in 

     if let dictionary = snapshot.value as? [String: AnyObject] { 
      let dish = Dish() 
      dish.setValuesForKeys(dictionary) 
      self.dishes.append(dish) 

      DispatchQueue.main.async { 
       self.tableView.reloadData() 
      } 
     } 

    }, withCancel: nil) 
} 
+0

请删除DispatchQueue.main .async。这不是必需的。 – Jay

+0

使用DispatchQueue.main.async是否有任何特定的负面影响?当我将self.tableview.reloadData()放入其中时,它明显加快了表加载信息的时间。 – JustTro11

+0

在这个应用程序中,它不应该有任何明显的性能差异,因为在从服务器收到Firebase数据并处理闭包中的代码之前,tableView不会被刷新。在这种情况下,您会在主串行队列上抛出一个异步任务,这会使任务按顺序排在前面。即不需要,因为Firebase功能已经是异步的。 – Jay

回答

0

几件事情:

火力地堡是异步的,你必须考虑到,在你的代码。就像在帖子中一样,第二个Firebase函数可能会在第一个Firebase函数成功返回数据之前执行,即当第二个调用发生时,restaurantName可能为零。

你应该嵌套你的调用(在这个用例中)以确保在使用它之前数据是有效的。这样的..并保持阅读

let ownersRef = rootRef.child("owners") 
let restaurantRef = rootRef.child("restaurants") 

func viewDidLoad() { 
    fetchOwner("owner uid") 
} 

func fetchOwner(ownerUid: String) { 

    var restaurantName: String? 

    let uid = FIRAuth.auth()?.currentUser?.uid 
    ownserRef.child(ownerUid).observeSingleEvent(of: .value, with: { snapshot in 

     if let dict = snapshot.value as? [String: AnyObject] { 
       restaurantId = dict["restaurant_id"] as? String 
       fetchRestaurant(restaurantId) 
      } 
     } 
    }) 
} 

func fetchRestaurant(restaurantId: String) { 
    restaurantRef.child(restaurantId).observeSingleEvent(of: .value, with: { snapshot in 

     if let dict = snapshot.value as? [String: AnyObject] { 
      let restaurantName = dict["name"] as! String 
      let menuDict = dict["menu"] as! [String:Any] 
      self.dataSourceArray.append(menuDict) 
      menuTableView.reloadData() 
     } 
    } 
} 

最重要的是,它几乎总是从它包含的数据撇清您的键名的最佳实践。在这种情况下,您将餐厅名称用作关键字。如果餐厅名称更改或更新会怎么样?你不能改变一个键!唯一的选择是删除它并重新编写它....并且...引用它的数据库中的每个节点。

利用childByAutoId并让Firebase为您命名节点并让孩子拥有相关数据的更好选择。

restaurants 
    -Yii9sjs9s9k9ksd 
     name: "Bobs Big Burger Barn" 
     owner: -Y88jsjjdooijisad 
     menu: 
     -y8u8jh8jajsd 
      name: "Belly Buster Burger" 
      type: "burger" 
      price: "$1M" 
     -j8u89joskoko 
      name: "Black and Blue Burger" 
      type: "burger" 
      price: "$9.95" 

正如你所看到的,我利用childByAutoId创建这家餐厅的关键,以及菜单上的项目。我还在所有者节点中引用了所有者的uid。

在这种情况下,如果腹部巴斯特汉堡改变腰部减肥汉堡,我们可以做一个改变,它已完成,任何引用它的东西也会更新。如果拥有者改变了,那么只需更改拥有者的uid即可。

如果餐厅名称更改为Tony's Taco Tavern,只需更改子节点即可。

希望有帮助!

编辑:回答一个评论:

为了获取字符串(即钥匙的 '钥匙':值对)立即.childByAutoId()创建

let testRef = ref.child("test").childByAutoId() 
    let key = testRef.key 
    print(key) 
+0

非常感谢Jay的帮助。真的很感激它。 – JustTro11

+0

后续问题:有没有办法立即存储由.childByAutoId生成的字符串,以便我可以在以下参考中使用它?或者我会不得不再次做这个嵌套函数?我知道一个快速解决方法是使用NSUUID()。uuidString,但它看起来不够优雅。 – JustTro11

+0

@ JustTro11当然!超级简单!我在答案的尾部添加了一些代码。 – Jay