问题 保持NDB数据库中属性的唯一性


NDB模型包含两个属性: email 和 password。如何避免向数据库添加两条相同的记录 email? NDB没有属性的UNIQUE选项,就像关系数据库那样。

检查新的 email 在添加之前不在数据库中 - 不满足我,因为两个并行进程可以同时进行检查并且每个都添加相同 email

我不确定交易可以在这里提供帮助,在阅读了一些手册之后,我对此印象不足。也许吧 同步交易?这是一次一个吗?


10017
2017-08-19 11:59


起源



答案:


通过电子邮件创建实体的密钥,然后使用 get_or_insert 检查是否存在。

还阅读有关密钥,实体的信息。 和 楷模

#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()

#Insert unique    
a = Person.get_or_insert(email)

或者如果你想检查一下

#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

保重。更改电子邮件将非常困难(需要创建新条目并将所有条目复制到新父级)。

为此,您可能需要将电子邮件存储在另一个实体中,并让User成为其中的父级。

另一种方法是使用Transactions并检查email属性。交易的工作方式:首先提交的是赢得的第一个。一个概念意味着如果2个用户只检查电子邮件,那么第一个(幸运的)将成功,因此您的数据将是一致的。


5
2017-08-19 12:14



使用get_or_insert()我不确定:要么我添加了新记录,要么我已经拥有了新记录。 - Graduate
改变电子邮件将是痛苦的... **或更好..不可能:)所以对于电子邮件,它不是一个好的解决方案,。 - Lipis
@Graduate是的应该是 key_a 修好了。 - Jimmy Kane
@Lipis是的,这将是a **的巨大痛苦。 - Jimmy Kane
我有 AttributeError: 'Key' object has no attribute 'put' - Graduate


答案:


通过电子邮件创建实体的密钥,然后使用 get_or_insert 检查是否存在。

还阅读有关密钥,实体的信息。 和 楷模

#ADD
key_a = ndb.Key(Person, email);
person = Person(key=key_a)
person.put()

#Insert unique    
a = Person.get_or_insert(email)

或者如果你想检查一下

#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

保重。更改电子邮件将非常困难(需要创建新条目并将所有条目复制到新父级)。

为此,您可能需要将电子邮件存储在另一个实体中,并让User成为其中的父级。

另一种方法是使用Transactions并检查email属性。交易的工作方式:首先提交的是赢得的第一个。一个概念意味着如果2个用户只检查电子邮件,那么第一个(幸运的)将成功,因此您的数据将是一致的。


5
2017-08-19 12:14



使用get_or_insert()我不确定:要么我添加了新记录,要么我已经拥有了新记录。 - Graduate
改变电子邮件将是痛苦的... **或更好..不可能:)所以对于电子邮件,它不是一个好的解决方案,。 - Lipis
@Graduate是的应该是 key_a 修好了。 - Jimmy Kane
@Lipis是的,这将是a **的巨大痛苦。 - Jimmy Kane
我有 AttributeError: 'Key' object has no attribute 'put' - Graduate


也许您正在寻找可以为您处理此问题的webapp2身份验证模块。它可以像这样导入 import webapp2_extras.appengine.auth.models。看 这里  一个完整的例子。


4
2017-08-19 19:51





我也遇到了这个问题,上面的解决方案没有解决我的问题:

  • 使它成为一把钥匙在我的情况下是不可接受的(我需要将来的财产变更)
  • 在电子邮件属性上使用事务不起作用AFAIK(您无法对事务内的非键名称进行查询,因此您无法检查电子邮件是否已存在)。

我最终创建了一个没有属性的独立模型,并将唯一属性(电子邮件地址)作为键名。在主模型中,我存储对电子邮件模型的引用(而不是将电子邮件存储为字符串)。然后,我可以通过按键查找电子邮件,使'change_email'成为检查唯一性的事务。


2
2017-07-27 14:22





这也是我遇到的事情,我决定改变@ Remko的解决方案。检查具有给定电子邮件的现有实体的主要问题是像op所述的潜在竞争条件。我添加了一个单独的模型,该模型使用电子邮件地址作为密钥,并具有保存令牌的属性。通过使用 get_or_insert,可以针对传入的令牌检查返回的实体令牌,如果它们匹配,则插入模型。

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.')

0
2017-09-01 18:38





我实现了一个通用结构来控制唯一属性。该解决方案可用于多种类型和属性。此外,这个解决方案对其他开发人员来说是透明的,他们像往常一样使用NDB方法put和delete。

1)Kind UniqueCategory:用于分组信息的唯一属性列表。例:

‘User.nickname’

2)种类唯一:它包含每个独特属性的值。关键是您要控制的属性值。我保存主实体的urlsafe而不是key或key.id(),因为它更实用,它没有父亲的问题,它可以用于不同的种类。例:

parent: User.nickname
key: AVILLA
reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)

3)Kind User:例如,我想控制电子邮件和昵称的唯一值。我创建了一个名为“uniqueness”的列表,其中包含唯一属性。我覆盖了处于事务模式的方法,并在删除一个实体时编写了钩子_post_delete_hook。

4)异常ENotUniqueException:重复某些值时引发的自定义异常类。

5)过程check_uniqueness:检查值是否重复。

6)过程delete_uniqueness:删除主实体时删除唯一值。

欢迎任何提示或改进。


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)

0
2018-04-05 11:53