我只是在探索新的Firebase Firestore,它包含一个称为引用的数据类型。我不清楚这是干什么的。

它像外键吗? 它可以用来指向位于其他地方的集合吗? 如果引用是一个实际的引用,我可以使用它查询吗?例如,我是否可以有一个直接指向用户的引用,而不是将userId存储在文本字段中?我可以使用这个用户引用进行查询吗?


当前回答

2022年更新

let coursesArray = [];
const coursesCollection = async () => {
    const queryCourse = query(
        collection(db, "course"),
        where("status", "==", "active")
    )
    onSnapshot(queryCourse, (querySnapshot) => {
        querySnapshot.forEach(async (courseDoc) => {

            if (courseDoc.data().userId) {
                const userRef = courseDoc.data().userId;
                getDoc(userRef)
                    .then((res) => {
                        console.log(res.data());
                    })
            }
            coursesArray.push(courseDoc.data());
        });
        setCourses(coursesArray);
    });
}

其他回答

如果不使用Reference数据类型,则需要更新每个文档。

例如,您有2个集合“categories”和“products”,并将类别名称“Fruits”存储到产品中“Apple”和“Lemon”的每个文档中,如下所示。但是,如果在categories中更新了categories名称“Fruits”,那么在product中每一个关于“Apple”和“Lemon”的文档中也需要更新category名称“Fruits”:

collection | document | field

categories > 67f60ad3 > name: "Fruits"
collection | document | field

  products > 32d410a7 > name: "Apple", category: "Fruits"
             58d16c57 > name: "Lemon", category: "Fruits"

但是,如果您将“Fruits”在categories中的引用存储到产品中“Apple”和“Lemon”的每个文档中,则当您在categories中更新类别名称“Fruits”时,您不需要更新“Apple”和“Lemon”的每个文档:

collection | document | field

  products > 32d410a7 > name: "Apple", category: categories/67f60ad3
             58d16c57 > name: "Lemon", category: categories/67f60ad3

这就是引用数据类型的优点。

很多答案提到它只是对另一个文档的引用,但不返回该引用的数据,但我们可以使用它单独获取数据。

下面是如何在firebase JavaScript SDK 9模块化版本中使用它的示例。

让我们假设您的壁炉有一个名为products的集合,其中包含以下文档。

{
  name: 'productName',
  size: 'medium',
  userRef: 'user/dfjalskerijfs'
}

在这里,用户有对用户集合中的文档的引用。我们可以使用下面的代码段获取产品,然后从引用检索用户。

import { collection, getDocs, getDoc, query, where } from "firebase/firestore";
import { db } from "./main"; // firestore db object

let productsWithUser = []
const querySnaphot = await getDocs(collection(db, 'products'));
querySnapshot.forEach(async (doc) => {
  let newItem = {id: doc.id, ...doc.data()};
  if(newItem.userRef) {
    let userData = await getDoc(newItem.userRef);
    if(userData.exists()) {
      newItem.userData = {userID: userData.id, ...userData.data()}
    }
    productwithUser.push(newItem);
  } else {
    productwithUser.push(newItem);
  }
});

这里的集合,getDocs, getDoc,查询,其中是与firestore相关的模块,我们可以在必要时使用它来获取数据。我们使用从产品文档返回的用户引用直接获取该引用的用户文档,使用以下代码:

let userData = await getDoc(newItem.userRef);

要阅读更多关于如何使用模块化ver SDK参考官方文档了解更多。

更新12/18/22 -我把它放在一个包里。

原创博客文章

What this package does is use RXJS to loop through each field in a document. If that document type is a Reference type, then it grabs that foreign document type. The collection version grabs the foreign key value for each reference field in all documents in your collection. You can also input the fields manually that you wish to parse to speed up the searching (see my post). This is definitely not as efficient as doing manual aggregations with Firebase Functions, as you will get charged for lots of reads for each document you read, but it could come in handy for people that want a quick way to join data on the frontend.

如果您缓存数据,并且实际上只需要这样做一次,这也可以派上用场。

J

安装

npm i j-firebase

进口

import { expandRef, expandRefs } from 'j-firebase';

https://github.com/jdgamble555/j-firebase


最初的发布


自动连接:

DOC

expandRef<T>(obs: Observable<T>, fields: any[] = []): Observable<T> {
  return obs.pipe(
    switchMap((doc: any) => doc ? combineLatest(
      (fields.length === 0 ? Object.keys(doc).filter(
        (k: any) => {
          const p = doc[k] instanceof DocumentReference;
          if (p) fields.push(k);
          return p;
        }
      ) : fields).map((f: any) => docData<any>(doc[f]))
    ).pipe(
      map((r: any) => fields.reduce(
        (prev: any, curr: any) =>
          ({ ...prev, [curr]: r.shift() })
        , doc)
      )
    ) : of(doc))
  );
}

集合

expandRefs<T>(
  obs: Observable<T[]>,
  fields: any[] = []
): Observable<T[]> {
  return obs.pipe(
    switchMap((col: any[]) =>
      col.length !== 0 ? combineLatest(col.map((doc: any) =>
        (fields.length === 0 ? Object.keys(doc).filter(
          (k: any) => {
            const p = doc[k] instanceof DocumentReference;
            if (p) fields.push(k);
            return p;
          }
        ) : fields).map((f: any) => docData<any>(doc[f]))
      ).reduce((acc: any, val: any) => [].concat(acc, val)))
        .pipe(
          map((h: any) =>
            col.map((doc2: any) =>
              fields.reduce(
                (prev: any, curr: any) =>
                  ({ ...prev, [curr]: h.shift() })
                , doc2
              )
            )
          )
        ) : of(col)
    )
  );
}

简单地把这个函数放在你的可观察对象周围,它会自动展开所有提供自动连接的引用数据类型。

使用

this.posts = expandRefs(
  collectionData(
    query(
      collection(this.afs, 'posts'),
      where('published', '==', true),
      orderBy(fieldSort)
    ), { idField: 'id' }
  )
);

注意:现在还可以输入想要展开的字段,作为数组中的第二个参数。

[’imageDoc’,’authorDoc’]

这将提高速度!

添加.pipe(带(1)).toPromise ();在最后为一个承诺版本!

更多信息请看这里。工作在Firebase 8或9!

简单!

J

迟来的是,这个博客有两个好处:

如果我希望按评分、发布日期或最多的赞来排序餐厅评论,我可以在评论子集合中做到这一点,而不需要复合索引。在更大的顶级集合中,我需要为其中的每一个创建一个单独的复合索引,而且我还有200个复合索引的限制。

我不会有200个综合指数,但有一些限制。

此外,从安全规则的角度来看,基于存在于父文档中的某些数据来限制子文档是相当常见的,当您将数据设置在子集合中时,这要容易得多。

例如,如果用户没有父字段的权限,则限制插入子集合。

根据#AskFirebase https://youtu.be/Elg2zDVIcLo?t=276 目前的主要用例是Firebase控制台UI中的链接