ndb 验证交易中的实体唯一性

ndb verify entity uniqueness in transaction

我一直在尝试创建具有 属性 的实体,它应该是唯一的或 None 类似于:

class Thing(ndb.Model):
  something = ndb.StringProperty()
  unique_value = ndb.StringProperty()

由于 ndb 无法指定 属性 应该是唯一的,所以我很自然地像这样手动执行此操作:

def validate_unique(the_thing):
  if the_thing.unique_value and Thing.query(Thing.unique_value == the_thing.unique_value).get():
      raise NotUniqueException 

这就像一个魅力,直到我想在我用于 creating/updating 实体的 ndb 事务中执行此操作。喜欢:

@ndb.transactional
def create(the_thing):
  validate_unique(the_thing)
  the_thing.put() 

但是 ndb 似乎只允许祖先查询,问题是我的模型没有 ancestor/parent。我可以执行以下操作来防止弹出此错误:

@ndb.non_transactional
def validate_unique(the_thing):
  ...

这感觉有点不合时宜,将某事声明为事务,然后让一个(重要的)部分在事务之外完成。我想知道这是要走的路还是有(更好的)选择。

还有一些关于为什么 ndb 只允许祖先查询的解释会很好。

由于您的唯一性检查涉及(全局)查询,这意味着它受制于 datastore's eventual consistency,这意味着它不会工作,因为查询可能无法检测到新创建的实体。

一个选择是切换到祖先查询,如果您的预期用途允许您使用这样的数据架构(或其他一些高度一致的方法)- 同一篇文章中有更多详细信息。

另一种选择是使用额外的数据作为临时缓存,您可以在其中存储 "a while" 的所有新创建实体的列表(让它们有足够的时间在全局中可见query) 除了查询结果中的那些之外,您还要在 validate_unique() 中签入。这样可以让你在事务外进行查询,只有在唯一性仍然可能的情况下才进入事务,但最终结果是手动检查缓存,在事务内(即事务内没有查询)。

存在第三个选项(以一些额外的存储消耗作为代价),基于数据存储对具有相同父级(或根本没有父级)的特定实体模型强制执行唯一实体 ID。您可以拥有这样的模型:

class Unique(ndb.Model):  # will use the unique values as specified entity IDs!
    something = ndb.BooleanProperty(default=False)

您可以像这样使用它(示例使用唯一父键,它允许为具有唯一值的多个属性重新使用模型,如果不需要,您可以完全删除父键):

@ndb.transactional
def create(the_thing):
    if the_thing.unique_value:
        parent_key = get_unique_parent_key()
        exists = Unique.get_by_id(the_thing.unique_value, parent=parent_key)
        if exists:
            raise NotUniqueException
        Unique(id=the_thing.unique_value, parent=parent_key).put()
    the_thing.put()


def get_unique_parent_key():

    parent_id = 'the_thing_unique_value'
    parent_key = memcache.get(parent_id)
    if not parent_key:
        parent = Unique.get_by_id(parent_id)
        if not parent:
            parent = Unique(id=parent_id)
            parent.put()
        parent_key = parent.key
        memcache.set(parent_id, parent_key)
    return parent_key