问题描述
表的结构为:
- 聊天
- --> randomId
- -->--> 参与者
- -->-->--> 0: 'name1'
- -->-->--> 1: 'name2'
- -->--> 聊天条目
等
我想要做的是查询聊天表,以通过传入的用户名字符串查找参与者的所有聊天记录.
What I am trying to do is query the chats table to find all the chats that hold a participant by a passed in username string.
这是我目前所拥有的:
subscribeChats(username: string) {
return this.af.database.list('chats', {
query: {
orderByChild: 'participants',
equalTo: username, // How to check if participants contain username
}
});
}
推荐答案
您当前的数据结构非常适合查找特定聊天的参与者.然而,它不是一个很好的结构来查找逆:用户参与的聊天.
Your current data structure is great to look up the participants of a specific chat. It is however not a very good structure for looking up the inverse: the chats that a user participates in.
这里有几个问题:
- 您正在将一个 set 存储为一个数组
- 您只能在固定路径上建立索引
一次聊天可以有多个参与者,因此您将其建模为一个数组.但这实际上并不是理想的数据结构.可能每个参与者只能参加一次聊天.但是通过使用数组,我可以:
A chat can have multiple participants, so you modelled this as an array. But this actually is not the ideal data structure. Likely each participant can only be in the chat once. But by using an array, I could have:
participants: ["puf", "puf"]
这显然不是您的想法,但数据结构允许.您可以尝试在代码和安全规则中保护这一点,但如果您从一个能更好地隐式匹配您的模型的数据结构开始,它会更容易.
That is clearly not what you have in mind, but the data structure allows it. You can try to secure this in code and security rules, but it would be easier if you start with a data structure that implicitly matches your model better.
我的经验法则:如果您发现自己在编写 array.contains()
,则应该使用集合.
My rule of thumb: if you find yourself writing array.contains()
, you should be using a set.
集合是一种结构,其中每个孩子最多只能出现一次,因此它自然可以防止重复.在 Firebase 中,您可以将集合建模为:
A set is a structure where each child can be present at most once, so it naturally protects against duplicates. In Firebase you'd model a set as:
participants: {
"puf": true
}
这里的 true
实际上只是一个虚拟值:重要的是我们已经将名称移到了键上.现在,如果我再次尝试加入此聊天,那将是一个空谈:
The true
here is really just a dummy value: the important thing is that we've moved the name to the key. Now if I'd try to join this chat again, it would be a noop:
participants: {
"puf": true
}
当你加入时:
participants: {
"john": true,
"puf": true
}
这是您需求的最直接表示:一个只能包含每个参与者一次的集合.
This is the most direct representation of your requirement: a collection that can only contain each participant once.
通过上述结构,您可以查询您所在的聊天记录:
With the above structure, you could query for chats that you are in with:
ref.child("chats").orderByChild("participants/john").equalTo(true)
问题是这需要你在 `participants/john" 上定义一个索引:
The problem is that this require than you define an index on `participants/john":
{
"rules": {
"chats": {
"$chatid": {
"participants": {
".indexOn": ["john", "puf"]
}
}
}
}
}
这将工作并且表现出色.但是现在每次有新人加入聊天应用程序时,您都需要添加另一个索引.这显然不是一个可扩展的模型.我们需要更改我们的数据结构以允许您想要的查询.
This will work and perform great. But now each time someone new joins the chat app, you'll need to add another index. That's clearly not a scaleable model. We'll need to change our data structure to allow the query you want.
第二条经验法则:对数据建模以反映您在应用中显示的内容.
由于您要显示用户的聊天室列表,请存储每个用户的聊天室:
Since you are looking to show a list of chat rooms for a user, store the chat rooms for each user:
userChatrooms: {
john: {
chatRoom1: true,
chatRoom2: true
},
puf: {
chatRoom1: true,
chatRoom3: true
}
}
现在您可以简单地确定您的聊天室列表:
Now you can simply determine your list of chat rooms with:
ref.child("userChatrooms").child("john")
然后遍历钥匙以获取每个房间.
And then loop over the keys to get each room.
您会希望在您的应用中有两个相关列表:
You'll like have two relevant lists in your app:
- 特定用户的聊天室列表
- 特定聊天室的参与者列表
在这种情况下,您还将在数据库中拥有这两个列表.
In that case you'll also have both lists in the database.
chatroomUsers
chatroom1
user1: true
user2: true
chatroom2
user1: true
user3: true
userChatrooms
user1:
chatroom1: true
chatroom2: true
user2:
chatroom1: true
user2:
chatroom2: true
我已将两个列表都拉到树的顶层,因为 Firebase 建议不要嵌套数据.
I've pulled both lists to the top-level of the tree, since Firebase recommends against nesting data.
在 NoSQL 解决方案中拥有两个列表是完全正常的.在上面的例子中,我们将 userChatrooms
称为 chatroomsUsers
的倒排索引.
Having both lists is completely normal in NoSQL solutions. In the example above we'd refer to userChatrooms
as the inverted index of chatroomsUsers
.
这是 Cloud Firestore 更好地支持此类查询的情况之一.它的 array-contains
运算符允许过滤在数组中具有特定值的文档,而 arrayRemove
允许您将数组视为一个集合.有关更多信息,请参阅Cloud Firestore 中的更好阵列一>.
This is one of the cases where Cloud Firestore has better support for this type of query. Its array-contains
operator allows filter documents that have a certain value in an array, while arrayRemove
allows you to treat an array as a set. For more on this, see Better Arrays in Cloud Firestore.
这篇关于Firebase 查询孩子的孩子是否包含值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!