关于firebase:firestore为noSQL和Flutter复制SQL Join

Firestore replicating a SQL Join for noSQL and Flutter

我意识到在复制NoSql文档数据库(例如FireStore)的联接方面存在很多问题,但是我无法找到将Dart / Flutter与FireStore结合使用的全面解决方案。

我进行了一些研究,我认为在以下示例中,我将寻找"多对多"关系(如果这是错的,请纠正我),因为将来可能需要查看所有个人资料以及所有连接。

在firebase中,我有两个根级别集合(配置文件


很遗憾,Cloud Firestore和其他NoSQL数据库中没有JOIN子句。在Firestore中,查询很浅。这意味着它们仅从运行查询的集合中获取项目。在单个查询中无法从两个顶级集合中获取文档。 Firestore不一次性支持跨不同集合的查询。单个查询只能使用单个集合中的文档属性。

因此,我能想到的最简单的解决方案是查询数据库以从profile集合中获取用户的uid。获得该ID后,请在回调中进行另一个数据库调用,并使用以下查询从connection集合中获取所需的相应数据:

1
stream: Firestore.instance.collection('connection').where('uid', isEqualTo:"xyc4567").snapshots(),

另一种解决方案是在每个用户下创建一个名为connection的子集合,并在其下添加所有connection对象。这种做法称为denormalization,是有关Firebase的常见做法。如果您是NoQSL数据库的新手,建议您观看此视频,对于更好的了解,Firebase数据库的反规范化是正常的。它用于Firebase实时数据库,但相同的规则适用于Cloud Firestore。

此外,在复制数据时,需要记住一件事。用与添加数据相同的方式,您需要对其进行维护。换句话说,如果要更新/删除项目,则需要在它存在的每个位置进行。


我做了这样的事情,将两个集合对象和类别的结果连接起来。

我做了两个StreamBuilder来显示在一个列表中,在第一个中,我获得了类别并放入了地图,然后我查询了对象并使用categoryID从地图中获得了类别对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
StreamBuilder<QuerySnapshot>(
              stream: Firestore.instance
                  .collection('categoryPath')
                  .snapshots(),
              builder: (BuildContext context,
                  AsyncSnapshot<QuerySnapshot> categorySnapshot) {
                //get data from categories

                if (!categorySnapshot.hasData) {
                  return const Text('Loading...');
                }

                //put all categories in a map
                Map<String, Category> categories = Map();
                categorySnapshot.data.documents.forEach((c) {
                  categories[c.documentID] =
                      Category.fromJson(c.documentID, c.data);
                });

                //then from objects

                return StreamBuilder<QuerySnapshot>(
                  stream: Firestore.instance
                      .collection('objectsPath')
                      .where('day', isGreaterThanOrEqualTo: _initialDate)
                      .where('day', isLessThanOrEqualTo: _finalDate)
                      .snapshots(),
                  builder: (BuildContext context,
                      AsyncSnapshot<QuerySnapshot> objectsSnapshot) {
                    if (!objectsSnapshot.hasData)
                      return const Text('Loading...');

                    final int count =
                        objectsSnapshot.data.documents.length;
                    return Expanded(
                      child: Container(
                        child: Card(
                          elevation: 3,
                          child: ListView.builder(
                              padding: EdgeInsets.only(top: 0),
                              itemCount: count,
                              itemBuilder: (_, int index) {
                                final DocumentSnapshot document =
                                    objectsSnapshot.data.documents[index];
                                Object object = Object.fromJson(
                                    document.documentID, document.data);

                                return Column(
                                  children: <Widget>[
                                    Card(
                                      margin: EdgeInsets.only(
                                          left: 0, right: 0, bottom: 1),
                                      shape: RoundedRectangleBorder(
                                        borderRadius: BorderRadius.all(
                                            Radius.circular(0)),
                                      ),
                                      elevation: 1,
                                      child: ListTile(
                                        onTap: () {},
                                        title: Text(object.description,
                                            style: TextStyle(fontSize: 20)),
//here is the magic, i get the category name using the map
of the categories and the category id from the object
                                        subtitle: Text(
                                          categories[object.categoryId] !=
                                                  null
                                              ? categories[
                                                      object.categoryId]
                                                  .name
                                              : 'Uncategorized',
                                          style: TextStyle(
                                              color: Theme.of(context)
                                                  .primaryColor),
                                        ),

                                      ),
                                    ),
                                  ],
                                );
                              }),
                        ),
                      ),
                    );

我不确定您想要的是什么还是明确的,但希望对您有所帮助。


假设您要使用依赖于某些Future objcets的Stream

1
2
3
4
5
6
7
8
9
10
Stories
     Document ID (Auto Generated) //Suppose,"zddgaXmdadfHs"
       > name ="The Lion & the Warthog"
       > coverImage ="https://...."
       > author ="Furqan Uddin Fahad"
       > publisDate = 123836249234
Favorites
     Document ID (Auto Generated)
       > storyDocID ="zddgaXmdadfHs" //Document ID of a story
       > userId ="adZXkdfnhoa" //Document ID of a user

SQL等效查询应如下所示

1
2
3
SELECT * FROM Favorites AS f, Stories AS s
WHERE f.storyDocID = s.DocumentID
AND f.userId = user.userId

这样的Firestore查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final _storeInstance = Firestore.instance;

Stream <List<Favorite>> getFavorites() async*  {
  final user = await _getUser(); //_getUser() Returns Future<User>
  yield* _storeInstance
      .collection('Favorites')
      .where('userId', isEqualTo: user.userId)
      .snapshots()
      .asyncMap((snapshot) async {
        final list = snapshot.documents.map((doc) async {
          final story = await _getStory(doc['storyDocID']);
          return Favorite.from(doc, story); //Favorite.from(DocumentSnapshot doc, Story story) returns an instance of Favorite
        }).toList(); //List<Future<Favorite>>
        return await Future.wait(list); //Converts List<Future<Favorite>> to Future<List<Favorite>>
      });
}

Future<Story> _getStory(String storyDocID) async {
  final docRef = _storeInstance
      .collection('Stories')
      .document(storyDocID);
  final document = await docRef.get();
  final story = Story.from(document);
  return story;
}

我认为不应该使用宗派主义,因为要保持这种称谓,就必须对Firestore进行额外的写操作

相反,jorge vieira是正确的,因为与写操作相比,您可以进行两次读操作

因此最好读取两次而不是两次写入数据,并且记住大型项目中每一个士气低落的事物也是非常不切实际的。