开发者问题收集

您可以创建动态 Firebase 查询吗?

2020-02-09
1754

我想像这样查询 firebase,但它不允许我这样做。有没有办法动态查询 firebase?我需要处理非常特殊的情况 - 例如,如果用户选择“无所谓”,那么我需要从查询中删除该首选项过滤器,一些字段是数组数据类型,一些是字符串,一些是数字。我需要能够处理所有形式的数据类型并适当地填充 where 子句。手动检查每个字段并调用每个字段将使系统负担过重(有超过 20 个字段需要查询)。任何建议都很好!

for(ddp in docDataPreferences){
  if(docDataPreferences[ddp] == "Doesn't Matter"){
    allDoesntMatterPreferences.push(ddp);
  } else if(docDataPreferences[ddp] != "Doesn't Matter" && docDataPreferences[ddp] != '' && docDataPreferences[ddp] != undefined) {
    keysWithValues.push(ddp);
    whereClause += ".where('preferences."+ ddp + "', 'array-contains-any', " + JSON.stringify(docDataPreferences[ddp]) +")"
  }
}

var usersMatchesCollection = config.db.collection("Users");
var query = usersMatchesCollection + whereClause;
await query.get().then( function (matchesQuerySnapshot) {
  matchesQuerySnapshot.forEach(function(doc) {
      //...do something
  })
})

TypeError: query.get is not a function. (In 'query.get()', 'query.get' is undefined)]

我可以看到 where 子句打印正确,但我认为您无法连接对象和字符串。当我手动添加返回的 whereClause 时,如下所示:

var query = usersMatchesCollection.where('preferences.datingPrefDistance', '==', 22).where('preferences.datingPrefDrinking', '==', "No").where('preferences.datingPrefEducation', '==', "Doctorate").where('preferences.datingPrefKids', '==', "No").where('preferences.datingPrefMarried', '==', "No").where('preferences.datingPrefSmokingCig', '==', "No").where('preferences.datingPrefSmokingPot', '==', "No").where('preferences.prefDrinking', '==', "No").where('preferences.prefEducation', '==', "Doctorate").where('preferences.prefIdentifyAs', '==', "Male").where('preferences.prefKids', '==', "No").where('preferences.prefMarried', '==', "No").where('preferences.prefSmokingPot', '==', "No")

它有效。因此,是连接导致了问题。

有解决方法吗?

2个回答

您似乎试图通过连接 CollectionReference 和字符串来构建查询,但这样做行不通。如果您 console.log(query) ,您将看到它是一个字符串,而不是 Query ,并且由于字符串上没有 get() 方法,这解释了您收到的错误。

要构建具有动态条件的查询,您应该遵循以下模式:

var usersMatchesCollection = config.db.collection("Users");
var query = usersMatchesCollection;

for(ddp in docDataPreferences){
  if(docDataPreferences[ddp] == "Doesn't Matter"){
    allDoesntMatterPreferences.push(ddp);
  } else if(docDataPreferences[ddp] != "Doesn't Matter" && docDataPreferences[ddp] != '' && docDataPreferences[ddp] != undefined) {
    keysWithValues.push(ddp);
    query = query.where('preferences.'+ ddp, 'array-contains-any', docDataPreferences[ddp]))
  }
}

await query.get().then( function (matchesQuerySnapshot) {
  matchesQuerySnapshot.forEach(function(doc) {
      //...do something
  })
})

这里的关键是 query = query.where 不断变化,将查询替换为每个条件的另一个查询,并添加新条件。然后,处理完所有条件后,您可以执行最终查询。

我试图确保代码与您的代码匹配,但该行中可能存在一两个语法错误,因为我不得不猜测 docDataPreferences 包含什么。

Frank van Puffelen
2020-02-09

Olivia,我经常使用 Frank 模式的变体 - 我传入一个数组,其中每个条目都是一个对象 {fieldref: string, opstr: string, value: value}。我的代码 .reduce 遍历数组,传递生成的扩展查询,如下所示:

/********************************************************
 * @function collectRecordsByFilter returns an array of documents from Firestore
 * 
 * @param table a properly formatted string representing the requested collection
 * - always an ODD number of elements
 * @param filterArray an array of argument object, each of the form:
 *        {
 *          fieldRef: string, // field name, or dotted field name
 *          opstr: string // operation string representation
 *          value: value // value for query operation
 *        }
 * @param ref (optional) allows "table" parameter to reference a sub-collection
 * of an existing document reference (I use a LOT of structered collections)
 * 
 * The array is assumed to be sorted in the correct order -
 * i.e. filterArray[0] is added first; filterArray[length-1] last
 * returns data as an array of objects (not dissimilar to Redux State objects)
 * with both the documentID and documentReference added as fields.
 */
export const collectRecordsByFilter = (table, filterArray, ref = null) => {
  const db = ref ? ref : firebase.firestore();

  //assumes filterArray is in processing order
  let query = db.collection(table);
  query = filterArray.reduce((accQuery, filter) => {
    return accQuery.where(filter.fieldRef, filter.opStr, filter.value);
  }, query);
  return query
    .get() //get the resulting filtered query results
    .then(querySnapshot => {
      return Promise.resolve(
        querySnapshot.docs.map(doc => {
          return {
            ...doc.data(),
            Id: doc.id,
            ref: doc.ref
          };
        })
      );
    })
    .catch(err => {
      return Promise.reject(err + ":collectRecordsByFilter");
    });
};

关键是“query”不是一种方法,而是一个查询对象,它有一个方法“where”,可以有效地将新条件附加到查询对象 - 它不是覆盖,而是扩展。

使用中:

export const fetchTourByDateAndArtist = (dateString, artist) => {
  //for each artist, only *one* tour bridges any particular data (except the default)
  const filterArray = [
    { fieldRef: "dateStart", opStr: "<=", value: dateString }
  ];
  return collectRecordsByFilter("Tours", filterArray, artist.ref).then(
    tours => {
      return Promise.resolve(
        tours.find(tour => {
          return moment(tour.dateEnd).isSameOrAfter(dateString);
        })
      );
    }
  );
};

这种方法为我抽象了 Firestore,并允许大量重复使用。

Tracy Hall (LeadDreamer)

LeadDreamer
2020-02-11