什么是Firebase Firestore '参考'数据类型好吗?

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

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

引用很像外键。

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

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

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

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

就像其他答案说的,它就像外键。但是,reference属性并不返回引用文档的数据。例如,我有一个产品列表,其中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解决方案的人来说,概念是,你需要在查询语句中使用一个“文档引用”对象

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

后来,这个博客有两个优点:

enter image description here

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

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

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

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

自动连接:

医生

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(); !

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

简单!

J

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

下面是如何在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);

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

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

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

collection | document | field


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


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

但是,如果你将类别中的提到“水果”;存储到产品“Apple"“Lemon"的每个文档中,当你在类别中更新类别名称“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);
});
}