Google app engine 维护NDB数据库中属性的唯一性
NDB模型包含两个属性:Google app engine 维护NDB数据库中属性的唯一性,google-app-engine,webapp2,app-engine-ndb,Google App Engine,Webapp2,App Engine Ndb,NDB模型包含两个属性:电子邮件和密码。如何避免使用相同的电子邮件向数据库中添加两条记录?NDB不像关系数据库那样对属性有唯一的选项 在添加之前检查新的电子邮件是否不在数据库中并不能满足我的要求,因为两个并行进程可以同时进行检查,并且每个进程都可以添加相同的电子邮件 我不确定交易是否有帮助,在阅读了一些手册后,我有这样的印象。可能是同步事务?它是指一次一个吗?通过电子邮件创建实体的密钥,然后使用检查是否存在 及 或者如果你想检查一下 #ADD key_a = ndb.Key(Person, em
电子邮件
和密码
。如何避免使用相同的电子邮件向数据库中添加两条记录?NDB不像关系数据库那样对属性有唯一的选项
在添加之前检查新的电子邮件
是否不在数据库中并不能满足我的要求,因为两个并行进程可以同时进行检查,并且每个进程都可以添加相同的电子邮件
我不确定交易是否有帮助,在阅读了一些手册后,我有这样的印象。可能是同步事务?它是指一次一个吗?通过电子邮件创建实体的密钥,然后使用检查是否存在
及
或者如果你想检查一下
#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()
#Check if it's added
new_key_a =ndb.Key(Person, email);
a = new_key_a.get()
if a is not None:
return
保重。更改电子邮件将非常困难(需要创建新条目并将所有条目复制到新的父条目)
为此,您可能需要将电子邮件存储在另一个实体中,并让用户成为该实体的父实体
另一种方法是使用事务并检查电子邮件属性。事务的工作方式:提交的第一个是第一个获胜的。这一概念意味着,如果两个用户检查电子邮件,只有第一个(幸运的)用户会成功,因此您的数据将是一致的 也许您正在寻找webapp2身份验证模块,它可以为您处理此问题。它可以这样导入导入webapp2\u extras.appengine.auth.models
。寻找一个完整的例子。我也遇到了这个问题,上面的解决方案并没有解决我的问题:
- 在我的情况下,将其作为密钥是不可接受的(我需要在将来更改该属性)
- 在email属性上使用transactions无法正常工作(您无法在transactions中查询非键名称,因此无法检查电子邮件是否已经存在)
我最终创建了一个单独的模型,没有属性,唯一的属性(电子邮件地址)作为密钥名。在主模型中,我存储对电子邮件模型的引用(而不是将电子邮件存储为字符串)。然后,我可以进行“更改电子邮件”交易,通过按键查找电子邮件来检查唯一性。这也是我遇到的问题,我选择了@Remko解决方案的变体。我用给定的电子邮件检查现有实体的主要问题是一个潜在的竞争条件,如op所述。我添加了一个单独的模型,它使用电子邮件地址作为密钥,并且有一个包含令牌的属性。通过使用,可以根据传入的令牌检查返回的实体令牌,如果它们匹配,则插入模型
import os
from google.appengine.ext import ndb
class UniqueEmail(ndb.Model):
token = ndb.StringProperty()
class User(ndb.Model):
email = ndb.KeyProperty(kind=UniqueEmail, required=True)
password = ndb.StringProperty(required=True)
def create_user(email, password):
token = os.urandom(24)
unique_email = UniqueEmail.get_or_insert(email,
token=token)
if token == unique_email.token:
# If the tokens match, that means a UniqueEmail entity
# was inserted by this process.
# Code to create User goes here.
# The tokens do not match, therefore the UniqueEmail entity
# was retrieved, so the email is already in use.
raise ValueError('That user already exists.')
我实现了一个通用结构来控制唯一的属性。此解决方案可用于多种类型和特性。此外,此解决方案对其他开发人员是透明的,他们像往常一样使用NDB方法put和delete
1) 种类唯一类别:用于分组信息的唯一属性列表。例如:
‘User.nickname’
2) 种类唯一:它包含每个唯一属性的值。键是您想要控制的自己的属性值。我保存了主实体的urlsafe,而不是key或key.id(),因为它更实用,并且与父实体没有问题,可以用于不同的类型。例如:
parent: User.nickname
key: AVILLA
reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)
3) 种类用户:例如,我想控制电子邮件和昵称的唯一值。我创建了一个名为“唯一性”的列表,它具有唯一的属性。我覆盖了事务模式下的方法,并在删除一个实体时编写了hook post delete hook
4) 异常:当某个值重复时引发的自定义异常类
5) 过程检查\唯一性:检查值是否重复
6) 过程删除唯一性:删除主实体时删除唯一值
欢迎任何提示或改进
使用get_或_insert()我不确定:要么我添加了一条新记录,要么我已经有了一条现有记录。更改电子邮件将是一件痛苦的事情**。。。或者更好。。不可能:)所以对于电子邮件来说,这不是一个好的解决方案,.@Graduate yes应该是key\u a
修复了这个问题。@Lipis yes这将是a**中的一个大麻烦。我有AttributeError:'key'对象没有属性“put”
parent: User.nickname
key: AVILLA
reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)
class UniqueCategory(ndb.Model):
# Key = [kind name].[property name]
class Unique(ndb.Model):
# Parent = UniqueCategory
# Key = property value
reference_urlsafe = ndb.StringProperty(required=True)
class ENotUniqueException(Exception):
def __init__(self, property_name):
super(ENotUniqueException, self).__init__('Property value {0} is duplicated'.format(property_name))
self. property_name = property_name
class User(ndb.Model):
# Key = Firebase UUID or automatically generated
firstName = ndb.StringProperty(required=True)
surname = ndb.StringProperty(required=True)
nickname = ndb.StringProperty(required=True)
email = ndb.StringProperty(required=True)
@ndb.transactional(xg=True)
def put(self):
result = super(User, self).put()
check_uniqueness (self)
return result
@classmethod
def _post_delete_hook(cls, key, future):
delete_uniqueness(key)
uniqueness = [nickname, email]
def check_uniqueness(entity):
def get_or_insert_unique_category(qualified_name):
unique_category_key = ndb.Key(UniqueCategory, qualified_name)
unique_category = unique_category_key.get()
if not unique_category:
unique_category = UniqueCategory(id=qualified_name)
unique_category.put()
return unique_category_key
def del_old_value(key, attribute_name, unique_category_key):
old_entity = key.get()
if old_entity:
old_value = getattr(old_entity, attribute_name)
if old_value != new_value:
unique_key = ndb.Key(Unique, old_value, parent=unique_category_key)
unique_key.delete()
# Main flow
for unique_attribute in entity.uniqueness:
attribute_name = unique_attribute._name
qualified_name = type(entity).__name__ + '.' + attribute_name
new_value = getattr(entity, attribute_name)
unique_category_key = get_or_insert_unique_category(qualified_name)
del_old_value(entity.key, attribute_name, unique_category_key)
unique = ndb.Key(Unique, new_value, parent=unique_category_key).get()
if unique is not None and unique.reference_urlsafe != entity.key.urlsafe():
raise ENotUniqueException(attribute_name)
else:
unique = Unique(parent=unique_category_key,
id=new_value,
reference_urlsafe=entity.key.urlsafe())
unique.put()
def delete_uniqueness(key):
list_of_keys = Unique.query(Unique.reference_urlsafe == key.urlsafe()).fetch(keys_only=True)
if list_of_keys:
ndb.delete_multi(list_of_keys)