我正在探索新的Firebase Firestore,它包含一种名为reference
的数据类型。目前还不清楚它的作用。
- 它像外键吗?
- 它能用于指向在其他位置的集合吗?
- 如果
reference
是实际引用,我能用它进行查询吗?例如,我能够有一个直接指向用户的引用,而不是在文本字段中存储userId吗?并且我能使用这个用户引用进行查询吗?
我正在探索新的Firebase Firestore,它包含一种名为reference
的数据类型。目前还不清楚它的作用。
reference
是实际引用,我能用它进行查询吗?例如,我能够有一个直接指向用户的引用,而不是在文本字段中存储userId吗?并且我能使用这个用户引用进行查询吗?以下是我在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) });
db.collection('products').get()
。你尝试过直接获取用户吗?我猜想 newItem.userRef.get()
应该可以替代 db.collection("users").doc(newItem.userRef.id).get()
。 - Sergey Nefedyevuid
并通过它引用来实现。 - Jürgen Brandstetterreference
,那么拥有它的意义是什么呢? - jean d'armeget()
方法。目前还不是很有用。希望他们能添加一个选项,自动将引用与相应的对象填充! - morgler引用与外键非常相似。
目前发布的SDK无法存储对其他项目的引用。在项目内,引用可以指向任何其他集合中的任何其他文档。
您可以像使用任何其他值一样在查询中使用引用:用于筛选、排序和分页(startAt/startAfter)。
与SQL数据库中的外键不同,引用不适用于在单个查询中执行联接。您可以将它们用于依赖查找(类似于联接),但请注意,每个跳转都会导致另一个到服务器的往返。
where('friends.myId', '==', true)
。 - Will如果你正在寻找一种JavaScript解决方案来查询引用,那么你需要在查询语句中使用“文档引用”对象。
teamDbRef = db.collection('teams').doc('CnbasS9cZQ2SfvGY2r3b'); /* CnbasS9cZQ2SfvGY2r3b being the collection ID */
//
//
db.collection("squad").where('team', '==', teamDbRef).get().then((querySnapshot) => {
//
}).catch(function(error) {
//
});
{
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,请参阅官方文档以获取更多信息。
如果您不使用引用数据类型,则需要更新每个文档。
例如,您有两个集合"categories"和"products",并且将类别名称"水果"存储在"categories"中,然后将"苹果"和"柠檬"的每个文档存储在"products"中,如下所示。但是,如果您更新"categories"中的类别名称"水果",您还需要更新"products"中"苹果"和"柠檬"的每个文档中的类别名称"水果":
collection | document | field
categories > 67f60ad3 > name: "Fruits"
collection | document | field
products > 32d410a7 > name: "Apple", category: "Fruits"
58d16c57 > name: "Lemon", category: "Fruits"
但是,如果您在产品
中的每个文档中将"水果"
的引用存储在类别
中,并将其与"苹果"
和"柠檬"
相关联,则在更新类别
中的名称"水果"
时,您不需要更新"苹果"
和"柠檬"
的每个文档:
collection | document | field
products > 32d410a7 > name: "Apple", category: categories/67f60ad3
58d16c57 > name: "Lemon", category: categories/67f60ad3
更新于12/18/22 - 我将其打包。
这个包使用RXJS循环遍历文档中的每个字段。如果该文档类型是引用类型,则获取该外部文档类型。集合版本获取集合中所有文档中每个引用字段的外键值。您还可以手动输入要解析以加快搜索速度的字段(请参阅我的帖子)。与使用Firebase函数手动聚合相比,这绝对不如效率高,因为您将为每个读取的文档付费大量读取费用,但对于想要在前端快速连接数据的人来说,这可能会很有用。
如果您缓存数据并且确实只需要执行一次此操作,则这也可能会很有用。
J
install
npm i j-firebase
导入
import { expandRef, expandRefs } from 'j-firebase';
https://github.com/jdgamble555/j-firebase
自动连接:
文档
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))
);
}
COLLECTION
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();
以获得Promise版本!
有关更多信息,请参见此处。 在Firebase 8或9中有效!
很简单!
J
map
和reduce
,这将增加显著的运行时复杂度。 - Abir Taheer2022更新
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);
});
}