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

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


引用很像外键。

当前发布的sdk不能存储对其他项目的引用。在项目中,引用可以指向任何其他集合中的任何其他文档。

您可以像使用任何其他值一样在查询中使用引用:用于过滤、排序和分页(startAt/startAfter)。

与SQL数据库中的外键不同,引用对于在单个查询中执行连接没有用处。您可以使用它们进行依赖查找(看起来像是连接),但要小心,因为每次跳转都会导致到服务器的另一次往返。

在Firestore中添加下面对我有用的引用。

As the other answers say, it's like a foreign key. The reference attribute doesn't return the data of the reference doc though. For example, I have a list of products, with a userRef reference as one of the attributes on the product. Getting the list of products, gives me the reference of the user that created that product. But it doesn't give me the details of the user in that reference. I've used other back end as a services with pointers before that have a "populate: true" flag that gives the user details back instead of just the reference id of the user, which would be great to have here (hopefully a future improvement).

下面是一些示例代码,我用来设置引用以及获得产品列表集合,然后从给定的用户引用id获得用户详细信息。

在集合上设置引用:

let data = {
  name: 'productName',
  size: 'medium',
  userRef: db.doc('users/' + firebase.auth().currentUser.uid)
};
db.collection('products').add(data);

获取一个集合(产品)和每个文档上的所有引用(用户详细信息):

db.collection('products').get()
    .then(res => {
      vm.mainListItems = [];
      res.forEach(doc => {
        let newItem = doc.data();
        newItem.id = doc.id;
        if (newItem.userRef) {
          newItem.userRef.get()
          .then(res => { 
            newItem.userData = res.data() 
            vm.mainListItems.push(newItem);
          })
          .catch(err => console.error(err));
        } else {
          vm.mainListItems.push(newItem);  
        }
        
      });
    })
    .catch(err => { console.error(err) });

对于那些寻找通过引用查询的Javascript解决方案的人来说,概念是,你需要在查询语句中使用一个“文档引用”对象

teamDbRef = db.collection('teams').doc('CnbasS9cZQ2SfvGY2r3b'); /* CnbasS9cZQ2SfvGY2r3b being the collection ID */
//
//
db.collection("squad").where('team', '==', teamDbRef).get().then((querySnapshot) => {
  //
}).catch(function(error) {
  //
});

(答案在这里:https://stackoverflow.com/a/53141199/1487867)

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

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

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

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

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

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

更新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

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

下面是如何在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参考官方文档了解更多。

如果不使用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

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

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