【问题标题】:What is Firebase Firestore 'Reference' data type good for?Firebase Firestore 'Reference' 数据类型有什么用?
【发布时间】:2018-03-16 01:59:03
【问题描述】:

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

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

【问题讨论】:

  • 我认为来自 firebase 团队的这段视频会为您分解:youtube.com/watch?v=Elg2zDVIcLo(从 4:36 开始观看)
  • 出于多种原因,我不喜欢在 firebase 中嵌套集合。如果由于某种原因您有另一个根级别的集合,您需要在同级根集合上一直向下钻取;比方说 4 个级别来获取文档。通过使用 refs 并仅使用 db.doc('some_saved_ref') 与从另一个根集合中再次匹配所有 id 相比,这变得容易得多。

标签: javascript node.js firebase google-cloud-platform google-cloud-firestore


【解决方案1】:

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

希望对你有帮助

【讨论】:

  • 感谢分享!我认为 Get 部分的第一行有错字,应该是db.collection('products').get()。您是否尝试过直接获取用户?我猜newItem.userRef.get() 应该工作而不是db.collection("users").doc(newItem.userRef.id).get()
  • 首先感谢您提供的示例。我希望他们将来会添加“填充:真实”。否则保存参考是毫无意义的。只需保存 uid 并通过它引用即可完成相同的操作。
  • 感谢您的示例!但是,如果我们在查询文档时没有“填充”类型的选项,那么存储引用类型有什么意义呢?如果有任何人知道的填充选项,请告诉我。
  • 所以实际上它不像外键。对我来说,它基本上没有任何作用 - 如果我们不能将其用作真正的外键,那么拥有 reference 有什么意义?
  • 所以reference 相对于string 的唯一优势是,您可以直接在引用上调用get()。还不是很有用。希望他们添加一个选项以使用相应的对象自动填充引用!
【解决方案2】:

引用很像外键。

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

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

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

【讨论】:

  • 拜托,你能分享一下可能的用例吗?是否可以查询该引用中的字段?例如。我有一个friends 集合,列出了我所有的朋友 (friends/myId)。然后,我在另一个文档 (group/groupId) 的 friends 字段中引用了该文档。我只想显示我在该组中的朋友,执行如下操作:where('friends.myId', '==', true)
  • 顺便说一句,更新docs 以包含添加引用类型的示例可能会很有用。
  • 我找不到任何相关信息?这将改变我的整个数据库结构,我需要了解更多...
  • 您有关于如何使用参考进行查询的示例(最好是快速的)?现在,我可以通过将原始 uid 存储为字符串来做到这一点,但这似乎不对。
  • 我需要将所有引用类型更改为字符串,因为查询总是会因引用类型而失败。我真的找不到任何关于如何按引用类型查询的信息:(如果有人知道如何按引用类型查询,请告诉我...
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这仍然不起作用 - 至少我无法在 UI 中发现它 :)
    • 为什么要在 Firebase 控制台 UI 中为链接的主要目的构建数据类型?
    【解决方案5】:

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

    这是一个如何在 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 文档返回的用户参考直接使用以下代码获取该参考的用户文档,

    let userData = await getDoc(newItem.userRef);
    

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

    【讨论】:

    • 请注意,尽管对 ref 的每个请求都将计入读取
    【解决方案6】:

    自动连接:

    文档

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

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

    简单!

    J

    【讨论】:

      【解决方案7】:

      迟来的this blog有两个好处:

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

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

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

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

      【讨论】:

        【解决方案8】:

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

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

        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""Lemon" 的每个文档类别中的类别名称“水果”

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-08
          相关资源
          最近更新 更多