Почему дубликаты в отношениях не нарушают UniqueConstraint? - PullRequest
0 голосов
/ 14 мая 2018

Почему с помощью следующих моделей удается добавить дубликаты связей в отношения во время одной и той же транзакции?Я ожидал (и нуждаюсь), что он потерпит неудачу с UniqueConstraint, помещенным в таблицу ассоциации.

Модели:

from app import db # this is the access to SQLAlchemy
class User(db.Model):

    id = db.Column(db.Integer, primary_key=True)

    sz_shirt_dress_sleeve = db.relationship(
        'SizeKeyShirtDressSleeve',
        secondary=LinkUserSizeShirtDressSleeve,
        backref=db.backref('users', lazy='dynamic'),
        order_by="asc(SizeKeyShirtDressSleeve.id)")

class SizeKeyShirtDressSleeve(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    size = db.Column(db.Integer)

    def __repr__(self):
        return 'Dress shirt sleeve size: %r' % self.size

LinkUserSizeShirtDressSleeve = db.Table(
    'link_user_size_shirt_dress_sleeve',
    db.Column(
        'size_id',
        db.Integer,
        db.ForeignKey('size_key_shirt_dress_sleeve.id'), primary_key=True),
    db.Column(
        'user_id',
        db.Integer,
        db.ForeignKey('user.id'), primary_key=True),
    db.UniqueConstraint('size_id', 'user_id', name='uq_association')
)

Из-за UniqueConstraint в таблице ассоциации я ожидал, что этот интерактивный сеанс будетвызвать ошибку IntegrityError.Это не позволяет и позволяет мне связать один и тот же размер дважды:

>>> from app.models import User, SizeKeyShirtDressSleeve
>>> db.session.add(User(id=8))
>>> db.session.commit()
>>> u = User.query.filter(User.id==8).one()
>>> u
<User id: 8, email: None, password_hash: None>
>>> u.sz_shirt_dress_sleeve
[]
>>> should_cause_error = SizeKeyShirtDressSleeve.query.first()
>>> should_cause_error
Dress shirt sleeve size: 3000
>>> u.sz_shirt_dress_sleeve.append(should_cause_error)
>>> u.sz_shirt_dress_sleeve.append(should_cause_error)
>>> u.sz_shirt_dress_sleeve
[Dress shirt sleeve size: 3000, Dress shirt sleeve size: 3000]
>>> db.session.commit()
>>> 

Подождите, что?Разве эти отношения не отражают то, что находится в моей таблице ассоциации?Думаю, мне следует проверить, что:

(сразу после того же сеанса)

>>> from app.models import LinkUserSizeShirtDressSleeve as Sleeve
>>> db.session.query(Sleeve).filter(Sleeve.c.user_id==8).all()
[(1, 8)]
>>>

Так что u.sz_shirt_dress_sleeve не точно представлял состояние таблицы ассоциации,...Хорошо.Но мне это нужно.На самом деле, я знаю, что это потерпит неудачу, если я попытаюсь добавить еще один should_cause_error объект в отношения:

>>> u.sz_shirt_dress_sleeve.append(should_cause_error)
>>> db.session.commit()
# huge stack trace
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: link_user_size_shirt_dress_sleeve.size_id, link_user_size_shirt_dress_sleeve.user_id [SQL: 'INSERT INTO link_user_size_shirt_dress_sleeve (size_id, user_id) VALUES (?, ?)'] [parameters: (1, 8)] (Background on this error at: http://sqlalche.me/e/gkpj)
>>> 

Отлично!Итак, вот что я нахожу: 1) Возможно иметь дублирующиеся элементы в списке отношений.2) Возможно, что список отношений не точно отражает состояние таблицы ассоциации, за которую он отвечает.3) UniqueConstraint работает ... до тех пор, пока я продолжаю взаимодействовать с отношениями в отдельных транзакциях (акцентировано на session.commit ()).

Вопросы: 1), 2) или 3)неправильно?И как я могу предотвратить дублирование элементов в моем списке отношений внутри одной и той же транзакции?

1 Ответ

0 голосов
/ 14 мая 2018

Эти три вещи верны.3) должен быть квалифицирован: UniqueConstraint всегда работает в том смысле, что ваша база данных никогда не будет несовместимой;он просто не выдаст ошибку , если только добавляемая связь не сброшена.

Основная причина этого - несоответствие импеданса между таблицей ассоциации в SQL и ее представлением.в SQLAlchemy.Таблица в SQL представляет собой мультимножество кортежей, поэтому с этим ограничением UNIQUE ваша таблица LinkUserSizeShirtDressSleeve представляет собой набор из (size_id, user_id) кортежей.С другой стороны, представление по умолчанию отношения в SQLAlchemy упорядочено list объектов, но оно накладывает некоторые ограничения на то, как он поддерживает этот список и как он ожидает, что вы будете взаимодействовать с этим списком, так что он ведет себя больше какset в некоторых отношениях.В частности, он молча игнорирует повторяющиеся записи в вашей таблице ассоциации (если у вас нет ограничения UNIQUE), и предполагает, что вы никогда не добавляете дублирующиеся объекты в этот список.

Если это проблема для вас, просто приведите поведение в большее соответствие с SQL, используя collection_class=set в ваших отношениях.Если вы хотите, чтобы при добавлении повторяющихся записей в отношение возникла ошибка, создайте пользовательский класс коллекции на основе set, который не срабатывает при повторных добавлениях.В некоторых моих проектах я прибегал к махинационному исправлению конструктора relationship, чтобы установить collection_class=set на всех моих отношений, чтобы сделать это менее многословным.

Вот как яБудет ли такой пользовательский класс коллекции:

class UniqueSet(set):
    def add(self, el):
        if el in self:
            raise ValueError("Value already exists")
        super().add(el)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...