小能豆

在 App Engine 中无需获取对象即可访问相关对象键

py

一般来说,对给定对象执行单个查询比执行多个查询更好。假设我有一堆“儿子”对象,每个对象都有一个“父亲”。我获取了所有“儿子”对象:

sons = Son.all()

然后,我想获取该组儿子的所有父亲。我会这样做:

father_keys = {}
for son in sons:
    father_keys.setdefault(son.father.key(), None)

然后我可以这样做:

fathers = Father.get(father_keys.keys())

现在,这假设 son.father.key() 实际上不会获取对象。我错了吗?我有一堆代码假设 object.related_object.key() 实际上不会从数据存储中获取 related_object。

我这样做对吗?


阅读 10

收藏
2024-11-14

共1个答案

小能豆

你在做的事情在大多数情况下是正确的,但关键在于理解 son.father.key() 的行为,特别是在不同的框架或 ORM(例如 Django、Google Datastore、SQLAlchemy 等)中。让我详细解释一下。

1. son.father.key() 的行为

在大多数 ORM 或数据存储框架中,son.father.key() 返回的是一个对象的“键”或“ID”,而不是完整的父对象本身。通常,这种方法是轻量级的操作,它仅获取对象的唯一标识符,而不会触发实际的数据库查询以加载完整的 Father 对象。

例如,假设你使用的是 Google Cloud Datastore 或类似的 NoSQL 数据存储,son.father.key() 只会获取父对象的唯一标识符(例如,Key 类型),并不会触发数据库查询来加载整个父对象。这个行为通常是符合预期的。

2. 触发查询的条件

如果你访问 son.father,例如 son.father.some_property,这通常会触发数据库查询,尤其是当 son.father 还没有被加载(即是一个“延迟加载”对象)时。所以,假设你只是获取 son.father.key(),它不会触发查询,但是如果你尝试访问父对象的其他属性,就会触发查询。

3. 你当前的做法

你当前的做法是:

  1. 获取所有的 Son 对象sons = Son.all()
  2. 收集每个 Son 对应父亲的 key():通过 father_keys.setdefault(son.father.key(), None) 你将父亲的 key() 存储在字典中。
  3. 根据父亲的 key() 获取所有父亲:通过 Father.get(father_keys.keys()) 获取所有父亲对象。

这种方法是合适的,因为你首先通过 key() 获取所有父亲对象的标识符,然后使用这些标识符在数据库中一次性查询所有父亲。这大大减少了查询次数,比为每个儿子单独查询父亲要高效得多。

4. 关于 setdefault() 的问题

你使用 setdefault() 是为了确保每个 key() 只出现一次。这是一个合理的做法,因为 setdefault() 会忽略已存在的键。你还需要确保在 father_keys 中不会包含 None(即没有父亲的儿子),否则你可能会在后续的 Father.get() 查询中遇到问题。

5. 改进的版本

可以稍微优化你的代码,使其更简洁,并确保避免无效的 None 值:

father_keys = set(son.father.key() for son in sons if son.father)
fathers = Father.get(father_keys)

这里:
- 我使用了集合推导式来确保每个父亲的 key() 只出现一次。
- 我添加了 if son.father 来确保只有当 son 确实有 father 时,才会把 key() 添加到集合中。

6. 结论

你的思路是正确的,如果你只访问 son.father.key(),并且这个方法不会触发加载父对象的数据,那么你的代码是高效且正确的。你通过一次查询获取所有父亲,避免了为每个儿子执行单独查询的低效做法。

不过,如果你后续访问 son.father 或其属性时需要触发查询,可能需要注意延迟加载的行为。因此,确保你只依赖于 key() 或通过批量查询来加载父对象,能有效避免过多的数据库查询。

2024-11-14