Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/firebase/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Firebase非规范化数据一致性问题_Firebase_Firebase Realtime Database_Ionic2_Ionic3_Angularfire2 - Fatal编程技术网

Firebase非规范化数据一致性问题

Firebase非规范化数据一致性问题,firebase,firebase-realtime-database,ionic2,ionic3,angularfire2,Firebase,Firebase Realtime Database,Ionic2,Ionic3,Angularfire2,我目前正在将Ionic CLI 3.19与Cordova CLI 7.1.0配合使用(@Ionic app script 3.1.4) 我目前面临的问题是,每次从其他地方更改相关数据时,我都应该同时更新friends节点值。我想用一些截图来澄清我的目标,让它更清楚 如下图所示,每个子节点都由一个用户数组组成,该用户数组的用户id作为好友节点的密钥。我之所以存储为数组,是因为每个用户都可能有很多朋友。 在这个例子中,Jeff Kim有一个朋友John Doe,反之亦然 当用户节点中的数据由于某种

我目前正在将Ionic CLI 3.19与Cordova CLI 7.1.0配合使用(@Ionic app script 3.1.4)

我目前面临的问题是,每次从其他地方更改相关数据时,我都应该同时更新friends节点值。我想用一些截图来澄清我的目标,让它更清楚

如下图所示,每个子节点都由一个用户数组组成,该用户数组的用户id作为好友节点的密钥。我之所以存储为数组,是因为每个用户都可能有很多朋友。 在这个例子中,Jeff Kim有一个朋友John Doe,反之亦然

当用户节点中的数据由于某种原因被更改时,我希望朋友节点中的相关数据也被更新

例如,当Jeff Kim更改其个人资料照片或statusMessage时,需要根据用户更改的内容更新驻留在friends节点中与Jeff Kim的uid匹配的所有相同uid

用户服务.ts

    constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
      this.afAuth.authState.do(user => {
      this.authState = user;
        if (user) {
          this.updateOnConnect();
          this.updateOnDisconnect();
        }
      }).subscribe();
     }

    sendFriendRequest(recipient: string, sender: User) {
      let senderInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email,
      timestamp: Date.now(),
      message: 'wants to be friend with you.'
    }
    return new Promise((resolve, reject) => {
      this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
      resolve({'status': true, 'message': 'Friend request has sent.'});
     }, error => reject({'status': false, 'message': error}));
  });
}

    fetchFriendRequest() {
    return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
  }

    acceptFriendRequest(sender: User, user: User) {
      let acceptedUserInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email
     }
     this.afDB.list(`friends/${sender.uid}`).push(user); 
     this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
     this.removeCompletedFriendRequest(sender.uid);
}
    getFriendList() {
      const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
        users.map(u => {
          this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
      });
      this.friends = users;
      console.log("FRIEND LIST@", users);
    });
    this.subscription.add(subscription);
  }
    testMultiPathStatusMessageUpdate({uid, statusMessage}) {
      if (uid === null || uid === undefined) 
      return;

      const rootRef = firebase.database().ref();
      const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

    return query.once('value').then(snapshot => {
      let key = Object.keys(snapshot.val());
      let updates = {};
      console.log("key:", key);
      key.forEach(key => {
        console.log("checking..", key);
        updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
      });
      updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
      return rootRef.update(updates);
    });
  }
根据我刚才看到的这一点,看起来我做了一件叫做
反规范化的事情
,解决方案可能是使用
多路径更新
来更改数据的一致性。然而,完全理解并开始编写一些代码有点棘手

我做了一些练习,以确保在不调用.update方法两次的情况下在多个位置更新数据

// I have changed updateUsername method from the code A to code B
// Code A
updateUsername(username: string) {
  let data = {};
  data[username] = this.currentUserId;
  this.afDB.object(`users/${this.currentUserId}`).update({'username': username});
  this.afDB.object(`usernames`).update(data);
}
// Code B
updateUsername(username: string) {
  const ref = firebase.database().ref(); 
  let updateUsername = {};
  updateUsername[`usernames/${username}`] = this.currentUserId; 
  updateUsername[`users/${this.currentUserId}/username`] = username;
  ref.update(updateUsername);
}
我并不是说这是一个完美的代码。但我一直试图自己解决这个问题,以下是我到目前为止所做的

假设我当前以Jeff的身份登录

当我运行这段代码时,朋友节点中与Jeff关联的所有数据都会更改,同时用户节点中Jeff的数据也会更新

该代码需要其他firebase专家进行改进,并且还应该在真实的测试代码上进行测试

根据以下内容,
once(
(通常,这对于Firebase的最佳性能来说是一个坏主意)。我应该找出这不好的原因。

朋友。ts

    constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
      this.afAuth.authState.do(user => {
      this.authState = user;
        if (user) {
          this.updateOnConnect();
          this.updateOnDisconnect();
        }
      }).subscribe();
     }

    sendFriendRequest(recipient: string, sender: User) {
      let senderInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email,
      timestamp: Date.now(),
      message: 'wants to be friend with you.'
    }
    return new Promise((resolve, reject) => {
      this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
      resolve({'status': true, 'message': 'Friend request has sent.'});
     }, error => reject({'status': false, 'message': error}));
  });
}

    fetchFriendRequest() {
    return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
  }

    acceptFriendRequest(sender: User, user: User) {
      let acceptedUserInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email
     }
     this.afDB.list(`friends/${sender.uid}`).push(user); 
     this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
     this.removeCompletedFriendRequest(sender.uid);
}
    getFriendList() {
      const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
        users.map(u => {
          this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
      });
      this.friends = users;
      console.log("FRIEND LIST@", users);
    });
    this.subscription.add(subscription);
  }
    testMultiPathStatusMessageUpdate({uid, statusMessage}) {
      if (uid === null || uid === undefined) 
      return;

      const rootRef = firebase.database().ref();
      const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

    return query.once('value').then(snapshot => {
      let key = Object.keys(snapshot.val());
      let updates = {};
      console.log("key:", key);
      key.forEach(key => {
        console.log("checking..", key);
        updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
      });
      updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
      return rootRef.update(updates);
    });
  }
用户服务.ts

    constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
      this.afAuth.authState.do(user => {
      this.authState = user;
        if (user) {
          this.updateOnConnect();
          this.updateOnDisconnect();
        }
      }).subscribe();
     }

    sendFriendRequest(recipient: string, sender: User) {
      let senderInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email,
      timestamp: Date.now(),
      message: 'wants to be friend with you.'
    }
    return new Promise((resolve, reject) => {
      this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
      resolve({'status': true, 'message': 'Friend request has sent.'});
     }, error => reject({'status': false, 'message': error}));
  });
}

    fetchFriendRequest() {
    return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
  }

    acceptFriendRequest(sender: User, user: User) {
      let acceptedUserInfo = {
      uid: sender.uid,
      displayName: sender.displayName,
      photoURL: sender.photoURL,
      statusMessage: sender.statusMessage,
      currentActiveStatus: sender.currentActiveStatus,
      username: sender.username,
      email: sender.email
     }
     this.afDB.list(`friends/${sender.uid}`).push(user); 
     this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
     this.removeCompletedFriendRequest(sender.uid);
}
    getFriendList() {
      const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
        users.map(u => {
          this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
      });
      this.friends = users;
      console.log("FRIEND LIST@", users);
    });
    this.subscription.add(subscription);
  }
    testMultiPathStatusMessageUpdate({uid, statusMessage}) {
      if (uid === null || uid === undefined) 
      return;

      const rootRef = firebase.database().ref();
      const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

    return query.once('value').then(snapshot => {
      let key = Object.keys(snapshot.val());
      let updates = {};
      console.log("key:", key);
      key.forEach(key => {
        console.log("checking..", key);
        updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
      });
      updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
      return rootRef.update(updates);
    });
  }
以下代码在将状态更新为联机而非脱机时工作正常

我认为这不是正确的方法

    updateOnConnect() {
      return this.afDB.object('.info/connected').valueChanges()
             .do(connected => {
             let status = connected ? 'online' : 'offline'
             this.updateCurrentUserActiveStatusTo(status)
             this.testMultiPathStatusUpdate(status)
             })
             .subscribe()
    }


    updateOnDisconnect() {
      firebase.database().ref().child(`users/${this.currentUserId}`)
              .onDisconnect()
              .update({currentActiveStatus: 'offline'});
      this.testMultiPathStatusUpdate('offline');
    }


    private statusUpdate(uid, status) {
      if (uid === null || uid === undefined) 
      return;

      let rootRef = firebase.database().ref();
      let query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);

      return query.once('value').then(snapshot => {
        let key = Object.keys(snapshot.val());
        let updates = {};
        key.forEach(key => {
          console.log("checking..", key);
          console.log("STATUS:", status);
          updates[`friends/${uid}/${key}/currentActiveStatus`] = status;
      });
      return rootRef.update(updates);
    });
  }

    testMultiPathStatusUpdate(status: string) {
      this.afDB.list(`friends/${this.currentUserId}`).valueChanges()
      .subscribe((users: any) => {
        users.map(u => {
          console.log("service U", u.uid);
          this.statusUpdate(u.uid, status);
        })
      })
    }

它确实会在控制台中显示
offline
,但更改不会显示在Firebase数据库中


有人能帮我吗?:(

我认为你做的非规范化是对的,你的多路径更新方向正确。但是假设几个用户可以有几个朋友,我错过了朋友表中的一个循环

您应该有表
users
friends
和一个
userfriends
。最后一个表就像是在
friends
中查找用户的快捷方式,您需要迭代每个朋友以找到需要更新的用户

在我的第一个应用程序示例[angular 4+firebase]中,我采用了不同的方法。我从客户端删除了该进程,并通过云函数中的onUpdate()将其添加到服务器中

在when user change his name中,云函数在用户已经编写的每个评论中执行并更新name。在我的例子中,客户端不知道非规范化

//Executed when user.name changes
exports.changeUserNameEvent = functions.database.ref('/users/{userID}/name').onUpdate(event =>{
    let eventSnapshot = event.data;
    let userID = event.params.userID;
    let newValue = eventSnapshot.val();

    let previousValue = eventSnapshot.previous.exists() ? eventSnapshot.previous.val() : '';

    console.log(`[changeUserNameEvent] ${userID} |from: ${previousValue} to: ${newValue}`);

    let userReviews = eventSnapshot.ref.root.child(`/users/${userID}/reviews/`);
    let updateTask = userReviews.once('value', snap => {
    let reviewIDs = Object.keys(snap.val());

    let updates = {};
    reviewIDs.forEach(key => { // <---- note that I loop in review. You should loop in your userFriend table
        updates[`/reviews/${key}/ownerName`] = newValue;
    });

    return eventSnapshot.ref.root.update(updates);
    });

    return updateTask;
});
问:你的意思是我应该创建一个查找表

在你的问题中提到的,David East给了我们一个如何去规范化的例子。最初,他有
用户
事件
。在去规范化过程中,他创建了一个类似vlookup的EventAttenders(就像你所说的)

问:你能给我举个例子吗

当然可以。我删除了一些用户信息并添加了一个额外字段
friendshipTypes

users
    xxsxaxacdadID1
        currentActiveStatus: online
        email: zinzzkak@gmail.com
        gender: Male
        displayName: Jeff Kim
        photoURL: https://firebase....
        ...
    trteretteteeID2
        currentActiveStatus: online
        email: hahehahaheha@gmail.com
        gender: Male
        displayName: Joeh Doe
        photoURL: https://firebase....
        ...

friends
    xxsxaxacdadID1
        trteretteteeID2
            friendshipTypes: bestFriend //<--- extra information
            displayName: Jeff Kim
            photoURL: https://firebase....
    trteretteteeID2
        xxsxaxacdadID1
            friendshipTypes: justAfriend //<--- extra information
            displayName: John Doe
            photoURL: https://firebase....


userfriends
    xxsxaxacdadID1
        trteretteteeID2: true
        hgjkhgkhgjhgID3: true
    trteretteteeID2
        trteretteteeID2: true
用户
xxsxaxacdadID1
当前活动状态:联机
电邮:zinzzkak@gmail.com
性别:男
显示名称:杰夫·金
照片网址:https://firebase....
...
trteretteeid2
当前活动状态:联机
电邮:hahehahaheha@gmail.com
性别:男
显示名称:Joeh Doe
照片网址:https://firebase....
...
朋友
xxsxaxacdadID1
trteretteeid2

Friendstypes:bestFriend//这是一个针对“新”用户的格式非常好的书面问题:)我希望有人能帮助你!欢迎使用堆栈溢出。@cramopy感谢你的评论。我真的希望如此。目前,我正在阅读此文章,但我的问题有点棘手。不确定该线程是否能帮助我。谢谢你给我提出建议。我不确定我是否正确构造了friends节点。我是否理解你的话是的,你的意思是我应该创建一个查找表?如果是的话,我应该把查找表放在哪里?你能给我一个例子吗?谢谢你的深入回答。因为一个问题,用户的活动状态永远不会变成脱机状态,我想也许我需要修复数据结构。而不是复制用户数据在friends节点中,只保留uid并使用这些ID从users节点获取用户数据。但是,问题是我需要将friends数据作为数组而不是对象获取。您认为这是解决问题的更好方法吗?如果您不需要在friends中添加字段(如Friendstypes),我认为您可以从两个表开始。
users
具有用户信息和
userFriends
具有指针(如我的示例中所示)…因此,在您的代码中,您首先获取当前用户拥有的所有朋友,然后获取所有朋友(或仅在线朋友)。我刚刚编辑了您的答案。如果我错了,请修复我:(问题是,它不能将所有用户作为一个包数组列表获取。。。