GAE数据存储区,如何在没有祖先或已知实体密钥的情况下进行原子查询和插入?

时间:2014-10-13 19:13:50

标签: python google-app-engine transactions google-cloud-datastore

例如我有这个用户模型:

class User(ndb.Model):
    email = ndb.StringProperty()

我希望仅在没有其他实体具有特定User属性值的情况下,以原子方式添加新的email实体。

在SQL中我可以做这样的事情来检查和插入事务:

begin; 
if count(select * from User where email='user@email.com') > 0 {
    insert into User values('user@email.com');
}
end;

但是在GAE中这是不允许的,因为在事务中不允许非祖先查询。我不想将电子邮件地址用作实体密钥,因为用户的稳定ID可能不是基于电子邮件地址。

3 个答案:

答案 0 :(得分:2)

这个问题已在webapp2微框架中得到解决。请查看this文档以及this博客文章。带有基本样本的复制粘贴代码:

from google.appengine.ext.ndb import model

class Unique(model.Model):
    @classmethod
    def create_multi(cls, values):
        keys = [model.Key(cls, value) for value in values]
        entities = [cls(key=key) for key in keys]
        func = lambda e: e.put() if not e.key.get() else None
        created = [model.transaction(lambda: func(e)) for e in entities]

        if created != keys:
            # A poor man's "rollback": delete all recently created records.
            model.delete_multi(k for k in created if k)
            return False, [k.id() for k in keys if k not in created]

        return True, []

    @classmethod
    def delete_multi(cls, values):
        return model.delete_multi(model.Key(cls, v) for v in values)

# Assemble the unique values for a given class and attribute scope.
uniques = [
    'User.username.%s' % username,
    'User.auth_id.%s' % auth_id,
    'User.email.%s' % email,
]

# Create the unique username, auth_id and email.
success, existing = Unique.create_multi(uniques)

if success:
    # The unique values were created, so we can save the user.
    user = User(username=username, auth_id=auth_id, email=email)
    user.put()
    return user
else:
    # At least one of the values is not unique.
    # Make a list of the property names that failed.
    props = [name.split('.', 2)[1] for name in uniques]
    raise ValueError('Properties %r are not unique.' % props)

答案 1 :(得分:1)

您可以在以下用例中遇到此问题:

  • 主题A检查电子邮件地址并收到回复"无"
  • 具有此电子邮件地址的实体通过主题B
  • 提交给数据存储区
  • 线程A还提交具有相同电子邮件地址的实体

解决此问题的一种方法是使用电子邮件地址作为实体名称(即密钥)。请注意,它不必是用户实体:它可以是自己的电子邮件实体 - 用户实体的子实体。它甚至可能根本没有其他属性,除非你需要它们。这样您就可以使用事务,因为电子邮件实体和用户实体属于同一个实体组。

答案 2 :(得分:0)

由于你没有祖先,你可能会在最后一次写作后碰到一些inconsistent state for some hundred milliseconds or so,但在现实生活中,当涉及到电子邮件时,这种情况非常罕见,但当然不可能发生

考虑到这一点,您可以通过以下方式检查电子邮件是否已存在:

if User.query(User.email == 'test@example.com').count() == 0:
  # do something with the email, it is safe to assume that is unique :)
else:
  # that email already taken