Сохранение уникальности свойства в базе данных NDB
Модель НБР содержит два свойства: email
и password
. Как избежать добавления в базу данных двух записей с одинаковыми email
? NDB не имеет уникального варианта для свойства, как это делают реляционные базы данных.
Проверка того, что new email
не находится в базе данных перед добавлением-не удовлетворит меня, потому что два параллельных процесса могут одновременно выполнять проверку и каждый добавлять один и тот же email
.
Я не уверен, что транзакции могут помочь здесь, я нахожусь под этим впечатлением после прочтения некоторые из руководств. Может быть, синхронные транзакции ? Означает ли это по одному за раз?
5 ответов:
Создайте ключ сущности по электронной почте, затем используйте 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
Берегите себя. Изменить адрес электронной почты будет очень сложно (нужно создать новую запись и скопировать все записи в новый родительский файл).
Для этой вещи, Возможно, вам нужно сохранить электронную почту в другой сущности и сделать так, чтобы пользователь был ее родителем.
Другой путь заключается в использовании транзакций и проверке свойства электронной почты. Транзакция работает следующим образом: первый, кто совершает коммит, - первый, кто выигрывает. Концепция, которая означает, что если 2 пользователя проверяют электронную почту, то только первый (удачливый) будет успешным, таким образом, ваши данные будут согласованы.
Возможно, вы ищете модуль webapp2-authentication, который может справиться с этим для вас. Его можно импортировать следующим образом
import webapp2_extras.appengine.auth.models
. Смотрите здесь для полного примера.
Я также столкнулся с этой проблемой, и решение выше не решило мою проблему:
- сделать его ключом в моем случае было неприемлемо (мне нужно, чтобы свойство было изменяемым в будущем)
- Использование транзакций в свойстве электронной почты не работает AFAIK (вы не можете делать запросы на неключевые имена внутри транзакций, поэтому вы не можете проверить, существует ли уже электронная почта).
В итоге я создал отдельную модель без свойств, а уникальное свойство (адрес электронной почты) в качестве ключевого имени. В основной модели я сохраняю ссылку на модель электронной почты (вместо того, чтобы хранить электронную почту в виде строки). Затем я могу сделать транзакцию "change_email", которая проверяет уникальность, просматривая электронную почту по ключу.
Это то, с чем я тоже столкнулся, и я остановился на вариации решения @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.')
Я реализовал универсальную структуру для управления уникальными свойствами. Это решение может быть использовано для нескольких видов и свойств. Кроме того, это решение прозрачно для других разработчиков, они используют методы NDB put и delete как обычно.
1) вид UniqueCategory: список уникальных свойств для группировки информации. Пример:
‘User.nickname’
2) Вид уникальный: он содержит значения каждого уникального свойства. Ключ - это собственное значение свойства, которым вы хотите управлять. Я экономлю urlsafe главной сущности вместо ключа или key.id () потому что это более практично, и у него нет проблем с родителем, и его можно использовать для разных видов. Пример:
parent: User.nickname key: AVILLA reference_urlsafe: ahdkZXZ-c3RhcnQtb3BlcmF0aW9uLWRldnINCxIEVXNlciIDMTIzDA (User key)
3) Добрый пользователь: например, я хочу контролировать уникальные значения для электронной почты и псевдонима. Я создал список под названием "уникальность" с уникальными свойствами. Я перезаписал метод, помещенный в транзакционный режим, и я написал крюк _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)