263

我只是在探索新的 Firebase Firestore,它包含一个名为reference. 我不清楚这是做什么的。

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

8 回答 8

173

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

正如其他答案所说,它就像一个外键。参考属性虽然不返回参考文档的数据。例如,我有一个产品列表,其中 userRef 引用作为产品的属性之一。获取产品列表,为我提供了创建该产品的用户的参考。但它没有给我该参考中用户的详细信息。我已经将其他后端用作带有指针的服务,之前有一个“populate:true”标志,它可以返回用户详细信息,而不仅仅是用户的参考 id,这将是很棒的(希望未来的改进) )。

下面是一些示例代码,我用来设置参考以及获取产品列表的集合,然后从给定的用户参考 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) });

希望这可以帮助

于 2017-12-06T11:36:24.030 回答
126

引用非常像外键。

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

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

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

于 2017-10-04T16:48:42.107 回答
26

对于那些寻找通过引用查询的 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 )

于 2019-05-19T20:50:54.770 回答
10

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

于 2020-09-21T08:19:52.407 回答
7

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

这是一个如何在 firebaseJavaScript SDK 9, modular版本中使用它的示例。

假设您的 Firestore 有一个名为的集合products,它包含以下文档。

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

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

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

这里collection, getDocs, getDoc, query, where是与 firestore 相关的模块,我们可以在必要时使用它来获取数据。我们使用从products文档直接返回的用户参考,使用以下代码获取该参考的用户文档,

let userData = await getDoc(newItem.userRef);

要阅读有关如何使用模块化版本 SDK 的更多信息,请参阅官方文档以了解更多信息。

于 2021-10-18T03:25:30.573 回答
2

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

例如,您有 2 个集合“categories”“products” ,您将类别名称“Fruits”存储到products“Apple”“Lemon”的每个文档中,如下所示。但是,如果您更新 categories 中的类别名称“ Fruits ,您还需要更新products中“Apple”“Lemon”的每个文档中的类别名称“Fruits”

collection | document | field

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

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

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

collection | document | field

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

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

于 2021-12-15T18:28:28.203 回答
2

自动加入:

文档

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

只需将此函数放在您的 observable 周围,它就会自动扩展所有提供自动连接的引用数据类型。

用法

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

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

['imageDoc', 'authorDoc']

这将提高速度!

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

请参阅此处了解更多信息。适用于 Firebase 8 或 9!

简单的!

Ĵ

于 2021-09-08T04:25:52.603 回答
0

迟来的,这个博客有两个优点:

在此处输入图像描述

如果我希望我希望按评级、发布日期或最多支持来订购餐厅评论,我可以在评论子集合中执行此操作,而无需综合索引。在较大的顶级集合中,我需要为其中的每一个创建一个单独的复合索引,并且我也有200 个复合索引的限制。

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

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

如果用户在父字段中没有权限,则一个示例是限制插入子集合。

于 2021-08-15T09:47:31.113 回答