什么是 Firebase Firestore 'Reference' 数据类型?

What is Firebase Firestore 'Reference' data type good for?

我正在探索新的 Firebase Firestore,它包含一个名为 reference 的数据类型。我不清楚这是做什么的。

引用非常像外键。

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

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

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

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

正如其他答案所说,它就像一个外键。但是,参考属性不 return 参考文档的数据。例如,我有一个产品列表,其中一个 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) });

希望对您有所帮助

对于那些正在寻找 Javascript 引用查询解决方案的人 - 概念是,您需要在查询语句中使用 'document reference' 对象

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

(感谢这里的回答:

根据#AskFirebase https://youtu.be/Elg2zDVIcLo?t=276 目前主要的 use-case 是 Firebase 控制台 UI

中的 link

迟来的,this blog有两个优点:

if I expect that I'll want to order restaurant reviews by rating, or publish date, or most upvotes, I can do that within a reviews subcollection without needing a composite index. In the larger top level collection, I'd need to create a separate composite index for each one of those, and I also have a limit of 200 composite indexes.

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

Also, from a security rules standpoint, it's fairly common to restrict child documents based on some data that exists in their parent, and that's significantly easier to do when you have data set up in subcollections.

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

自动加入:

文档

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(take(1)).toPromise();以获得承诺版本!

有关详细信息,请参阅 here。适用于 Firebase 8 或 9!

简单!

J

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

这是一个如何在 firebase JavaScript 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 文档中的用户引用 returned 直接使用以下代码获取该引用的用户文档,

let userData = await getDoc(newItem.userRef);

要了解有关如何使用模块化版本 SDK 的更多信息,请参阅 official documentation 以了解更多信息。

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

例如,您有 2 个集合 "categories""products",并且您存储了类别名称 类别中的“水果”“苹果”“柠檬”的每个文档] 在 products 中,如下所示。但是,如果您在 类别 中更新类别名称 "Fruits",您还需要更新类别名称 "Fruits" “Apple”“Lemon”products 的每个文档中:

collection | document | field

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

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

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

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