【问题标题】:Firebase denormalization data consistency issueFirebase 非规范化数据一致性问题
【发布时间】:2017-12-11 20:29:52
【问题描述】:

我目前正在使用 Ionic CLI 3.19 和 Cordova CLI 7.1.0 (@ionic-app-script 3.1.4)

我目前面临的问题是,每次相关数据从其他地方发生变化时,我都应该同时更新好友节点值。我想通过一些屏幕截图来阐明我的目标,以使其更加清晰。

从下图中可以看出,每个子节点都包含一个用户数组,其中用户 id 作为好友节点的键。我存储为数组的原因是因为每个用户可以有很多朋友。 在此示例中,Jeff Kim 有一个朋友 John Doe,反之亦然。

当用户节点中的数据由于某种原因发生变化时,我希望朋友节点中的相关数据也希望它们也被更新。

例如,当 Jeff Kim 更改他的头像或 statusMessage 时,所有与 Jeff Kim 的 uid 匹配的朋友节点中的相同 uid 都需要根据用户的更改进行更新。

user-service.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);
}

根据我刚刚看到的这个clip,看起来我做了一个名为Denormalization 的事情,解决方案可能是使用Multi-path updates 来一致地更改数据。 Data consistency with Multi-path 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 专家改进,并且应该在真实的测试代码上进行测试。

根据以下threadonce('value'(一般来说,这对于使用 Firebase 获得最佳性能来说是个坏主意)。 我应该找出这不好的原因。

friend.ts

    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);
  }

user-service.ts

    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 数据库中。

有人可以帮助我吗? :(

【问题讨论】:

  • 对于“新”用户来说,这是一个格式和书面非常好的问题:) 我希望有人可以帮助你!欢迎使用 Stack Overflow。
  • @cramopy 感谢您的评论。我真的希望如此。目前,我正在阅读这个thread 但我的问题有点棘手。不确定该线程是否可以帮助我。

标签: firebase firebase-realtime-database ionic2 ionic3 angularfire2


【解决方案1】:

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

您应该有表usersfriendsuserFriend。最后一张表就像是在friends 中查找用户的快捷方式,但您需要遍历每个朋友才能找到需要更新的用户。

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

code bellow 中,当用户更改其姓名时,云功能会在用户已写的每条评论中执行并更新名称。在我的情况下,客户端不知道非规范化。

//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;
});

编辑

问:我的朋友节点结构是否正确

我更喜欢只复制(非规范化)我更经常需要的信息。按照这个想法,例如,您应该只复制“userName”和“photoURL”。您可以分两步访问所有朋友的信息:

 let friends: string[];
 for each friend in usrService.getFriend(userID)
    friends.push(usrService.getUser(friend))

问:你的意思是我应该创建一个查找表?

你的问题中提到的clip,David East 给我们举了一个如何去规范化的例子。原来他有usersevents。在非规范化中,他创建了类似于 vlookup 的 eventAttendees(就像你悲伤一样)。

问:你能给我举个例子吗?

当然。我删除了一些用户的信息并添加了一个额外的字段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

【讨论】:

  • 感谢您给我的建议。我不太确定我是否正确构建了朋友节点。如果我正确理解你的话,你的意思是我应该创建一个查找表?如果是这样,我应该在哪里放置查找表?可以举个例子吗?
  • 感谢您的深入解答。由于用户的活动状态永远不会变成离线状态的问题,我心想也许我需要修复数据结构。
  • 与其将用户数据复制到好友节点,只需保留 uid 并使用这些 id 从用户节点获取用户数据。但是,问题是我需要将朋友数据作为数组而不是对象获取。您认为这是解决问题的更好方法吗?
  • 如果您不需要在朋友中添加字段(如friendshipTypes),我认为您可以从两张表开始。 users 有用户信息,userFriends 有指针(就像在我的例子中一样)......所以在你的代码中,你首先获取当前用户拥有的所有朋友,然后你获取每个朋友(或只是在线朋友)。
  • 我刚刚编辑了您的答案。如果我错了,请纠正我。 :( 问题是,它不会将所有用户作为一个包数组列表...
猜你喜欢
  • 1970-01-01
  • 2016-12-27
  • 1970-01-01
  • 1970-01-01
  • 2017-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-15
相关资源
最近更新 更多