【问题标题】:Maintain uniqueness of a property in the NDB database维护 NDB 数据库中属性的唯一性
【发布时间】:2013-08-21 05:13:23
【问题描述】:

NDB 模型包含两个属性:emailpassword。如何避免将两个具有相同email 的记录添加到数据库中? NDB 没有属性的 UNIQUE 选项,就像关系数据库一样。

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

我不确定事务在这里是否有帮助,在阅读了一些手册后,我有这种印象。也许是同步交易?是一次一个吗?

【问题讨论】:

    标签: google-app-engine webapp2 app-engine-ndb


    【解决方案1】:

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

    Also read about keys , entities.models

    #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
    

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

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

    另一种方法是使用事务并检查电子邮件属性。事务的工作方式:第一个提交是第一个获胜。这个概念意味着如果 2 个用户检查电子邮件,只有第一个(幸运的)用户会成功,因此您的数据将是一致的。

    【讨论】:

    • 使用 get_or_insert() 我不能确定:我添加了一条新记录还是已有一条记录。
    • 更改电子邮件将是痛苦的 **... 或更好.. 不可能 :) 所以对于电子邮件这不是一个好的解决方案。
    • @Graduate yes 应该是key_a 修复了。
    • @Lipis 是的,这将是一个很大的痛苦。
    • 我有AttributeError: 'Key' object has no attribute 'put'
    【解决方案2】:

    也许您正在寻找可以为您处理此问题的 webapp2-authentication 模块。可以这样导入import webapp2_extras.appengine.auth.models。查看here 获取完整示例。

    【讨论】:

      【解决方案3】:

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

      • 在我的情况下,将其设为密钥是不可接受的(我需要将来更改该属性)
      • 在电子邮件属性上使用事务不起作用 AFAIK(您无法对事务中的非键名称进行查询,因此无法检查电子邮件是否已存在)。

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

      【讨论】:

        【解决方案4】:

        这也是我遇到的问题,我选择了@Remko 解决方案的变体。我使用给定电子邮件检查现有实体的主要问题是潜在的竞争条件,如操作所述。我添加了一个单独的模型,它使用电子邮件地址作为键,并具有一个保存令牌的属性。通过使用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.')
        

        【讨论】:

          【解决方案5】:

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

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

          ‘User.nickname’
          

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

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

          3) Kind User:例如,我想控制电子邮件和昵称的唯一值。我创建了一个名为“唯一性”的列表,其中包含独特的属性。我覆盖了置于事务模式的方法,并在删除一个实体时编写了钩子 _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)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-05-19
            • 1970-01-01
            • 2023-03-19
            • 1970-01-01
            • 2016-04-13
            • 2016-07-19
            • 1970-01-01
            相关资源
            最近更新 更多