创建实体模型

您需要为实体创建一个模型类。可以通过以下两种方法实现此目的:

  • 创建一个定义实体属性的模型类。
  • 创建一个不会预先定义实体的 Expando 模型类。

本文档介绍了如何创建上述各种模型类。另外还介绍了如何创建模型钩子,以便应用可以在某种类型的操作之前或之后运行某个代码,例如,在每个 get() 之前。

创建带有属性的模型类

在创建实体之前,您必须创建一个模型类,以定义一个或多个实体属性。例如:

from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

其中,您希望每个 Account 实体都具有用户名、用户 ID 和电子邮件属性。

如需查看属性类型的完整列表,请参阅实体属性参考

创建 Expando 模型类

您并非必须使用预先定义了属性的模型类。名为 Expando 的特殊模型子类会更改其实体的行为,以这种方式分配的任何属性都将保存到数据存储区。请注意,此类属性不能以下划线开头。

下面演示了如何创建 Expando 模型:

class Mine(ndb.Expando):
    pass
...
e = Mine()
e.foo = 1
e.bar = 'blah'
e.tags = ['exp', 'and', 'oh']
e.put()

这会将实体写入数据存储区,其中,foo 属性为整数值 1bar 属性为字符串值 'blah',重复的 tag 属性为字符串值 'exp''and''oh'。这些属性已编入索引,您可以使用此实体的 _properties 属性进行检查:

return e._properties
# {
#     'foo': GenericProperty('foo'),
#     'bar': GenericProperty('bar'),
#     'tags': GenericProperty('tags', repeated=True)
# }

通过从数据存储区获取值而创建的 Expando 会具备保存在数据存储区中的所有属性值。

应用可以向 Expando 子类添加预定义属性:

class FlexEmployee(ndb.Expando):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
...
employee = FlexEmployee(name='Sandy', location='SF')

这为 employee 赋予了一个值为 'Sandy'name 属性,一个值为 Noneage 属性,以及一个值为 'SF' 的动态属性 location

要创建 Expando 子类,并且不希望将其属性编入索引,您可以在子类定义中设置 _default_indexed = False

class Specialized(ndb.Expando):
    _default_indexed = False
...
e = Specialized(foo='a', bar=['b'])
return e._properties
# {
#     'foo': GenericProperty('foo', indexed=False),
#     'bar': GenericProperty('bar', indexed=False, repeated=True)
# }

您还可以在 Expando 实体上设置 _default_indexed:此操作会影响设置后分配的所有属性。

另一种实用的方法是,查询 Expando 种类中的某个动态属性。如下所示:

FlexEmployee.query(FlexEmployee.location == 'SF')

上述查询将无法工作,因为该类的 location 属性没有属性对象。不过,您可以使用 GenericPropertyExpando 类将其用于动态属性):

FlexEmployee.query(ndb.GenericProperty('location') == 'SF')

使用模型钩子

NDB 提供了轻量级钩子机制。通过定义一个钩子,应用可以在某种类型的操作之前或之后运行某个代码;例如,Model 可能会在每个 get() 之前运行某个函数。

当使用相应方法的同步、异步和多个版本时,便会运行钩子函数。例如,“pre-get”钩子将应用于所有 get()get_async()get_multi()。每个钩子都有 RPC 前和 RPC 后版本。

钩子可用于以下方面:

  • 查询缓存
  • 审核每个用户的 Cloud Datastore 活动
  • 模拟数据库触发器

以下示例演示了如何定义钩子函数:

from google.appengine.ext import ndb
...
class Friend(ndb.Model):
    name = ndb.StringProperty()

    def _pre_put_hook(self):
        _notify('Gee wiz I have a new friend!')

    @classmethod
    def _post_delete_hook(cls, key, future):
        _notify('I have found occasion to rethink our friendship.')
...
f = Friend()
f.name = 'Carole King'
f.put()  # _pre_put_hook is called
fut = f.key.delete_async()  # _post_delete_hook not yet called
fut.get_result()  # _post_delete_hook is called

如果您将 post-hook 用于异步 API,则可以通过调用 check_result()get_result() 或生成(在 tasklet 中)异步方法的 future,来触发钩子。Post hook 不会检查远程过程调用 (RPC) 是否成功;无论失败与否,钩子都会运行。

所有 post-hook 在调用签名末尾都有一个 Future 参数。此 Future 对象包含操作的结果。您可以针对此 Future 调用 get_result(),以检索结果;get_result() 一定不会受阻,因为在调用钩子时,Future 便已处于完成状态。

在 pre-hook 阻止请求发生期间会引发异常。尽管在 <var>*</var>_async 方法中触发了钩子,但您无法通过在 pre-RPC 钩子中引发 tasklets.Return 来预先执行远程过程调用 (RPC)。

后续步骤