Синтаксис для позднесвязывающих само-ссылочных отношений «многие ко многим» - PullRequest
0 голосов
/ 26 февраля 2019

Я нашел много объяснений того, как создать самосвязанную связь «многие ко многим» (для последователей или друзей пользователя), используя отдельную таблицу или класс:

Ниже приведены три примера, один из которых принадлежит Майку.Сам Байер:

Но в каждом примере яобнаружено, что синтаксис для определения primaryjoin и secondaryjoin в отношениях является ранним обязательным:

# this relationship is used for persistence
friends = relationship("User", secondary=friendship, 
                       primaryjoin=id==friendship.c.friend_a_id,
                       secondaryjoin=id==friendship.c.friend_b_id,
)

Это прекрасно работает, за исключением одного обстоятельства: когда используется класс Baseчтобы определить столбец id для всех ваших объектов, как показано в Миксины: добавление базы из документов

Мой Base класс и таблица followers определены следующим образом:

from flask_sqlchalchemy import SQLAlchemy
db = SQLAlchemy()

class Base(db.Model):
    __abstract__ = True
    id = db.Column(db.Integer, primary_key=True)

user_flrs = db.Table(
    'user_flrs',
    db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('followed_id', db.Integer, db.ForeignKey('user.id')))

Но теперь у меня есть трУдвойте отношения с последователями, которые некоторое время верноподданно служили мне, прежде чем я переместил id в миксин:

class User(Base):
    __table_name__ = 'user'
    followed_users = db.relationship(
        'User', secondary=user_flrs, primaryjoin=(user_flrs.c.follower_id==id),
        secondaryjoin=(user_flrs.c.followed_id==id),
        backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')

db.class_mapper(User)  # trigger class mapper configuration

Предположительно, потому что id отсутствует в локальной области, хотякажется, для этого выдается странная ошибка:

ArgumentError: Не удалось найти какие-либо простые выражения равенства, включающие локально сопоставленные столбцы внешнего ключа для условия первичного соединения 'user_flrs.follower_id = :follower_id_1' в отношении User.followed_users.Убедитесь, что ссылающиеся столбцы связаны с ForeignKey или ForeignKeyConstraint или помечены в условии соединения аннотацией foreign().Чтобы разрешить операторам сравнения, отличным от '==', отношение может быть помечено как viewonly=True.

И оно выдает ту же ошибку, если я заменю скобки на кавычки, чтобы воспользоваться преимуществами позднего связывания.Я понятия не имею, как аннотировать эту вещь с помощью foreign() и remote(), потому что я просто не знаю, что sqlalchemy хотел бы, чтобы я описал как чужеродные и удаленные в отношении самоссылки, которое пересекает вторичную таблицу!Я перепробовал множество комбинаций этого, но до сих пор это не сработало.

У меня была очень похожая (хотя и не идентичная) проблема с самоотносительными отношениями, которая не охватить отдельную таблицу, и ключом было просто преобразовать аргумент remote_side в аргумент с поздней привязкой.Это имеет смысл для меня, поскольку столбец id отсутствует во время процесса раннего связывания.

Если у меня возникли проблемы с поздним связыванием, пожалуйста, сообщите.Однако в текущей области я понимаю, что id сопоставлен со встроенной в Python id() и, следовательно, не будет работать в качестве отношения раннего связывания.

Преобразование id в Base.id вобъединение приводит к следующей ошибке:

ArgumentError: Не удалось найти какие-либо простые выражения равенства, включающие локально сопоставленные столбцы внешнего ключа для условия первичного соединения 'user_flrs.follower_id = "<name unknown>"' в отношении User.followed_users.Убедитесь, что ссылающиеся столбцы связаны с ForeignKey или ForeignKeyConstraint или помечены в условии соединения аннотацией foreign().Чтобы разрешить операторам сравнения, отличным от '==', отношение может быть помечено как viewonly=True.

1 Ответ

0 голосов
/ 26 февраля 2019

Вы не можете использовать id в своих фильтрах объединения, нет, потому что это встроенная функция id() *1003* , а не столбец User.id.

У вас естьтри варианта:

  1. Определить отношение после создания вашей модели User, присвоения ее новому атрибуту User;затем вы можете ссылаться на User.id, как он был извлечен из базы:

    class User(Base):
        # ...
    
    User.followed_users = db.relationship(
        User,
        secondary=user_flrs,
        primaryjoin=user_flrs.c.follower_id == User.id,
        secondaryjoin=user_flrs.c.followed_id == User.id,
        backref=db.backref('followers', lazy='dynamic'),
        lazy='dynamic'
    )
    
  2. Использовать строки для выражений соединения.Любой аргумент relationship(), являющийся строкой, при настройке преобразователя оценивается как выражение Python, а не только первый аргумент:

    class User(Base):
        # ...
    
        followed_users = db.relationship(
            'User',
            secondary=user_flrs,
            primaryjoin="user_flrs.c.follower_id == User.id",
            secondaryjoin="user_flrs.c.followed_id == User.id",
            backref=db.backref('followers', lazy='dynamic'),
            lazy='dynamic'
        )
    
  3. Определите отношения как вызываемые;они вызываются во время конфигурирования mapper для создания конечного объекта:

    class User(Base):
        # ...
    
        followed_users = db.relationship(
            'User',
            secondary=user_flrs,
            primaryjoin=lambda: user_flrs.c.follower_id == User.id,
            secondaryjoin=lambda: user_flrs.c.followed_id == User.id,
            backref=db.backref('followers', lazy='dynamic'),
            lazy='dynamic'
        )
    

Для двух последних опций см. документацию sqlalchemy.orgm.relationship() :

Некоторые аргументы, принимаемые relationship(), по выбору принимают вызываемую функцию, которая при вызове выдает желаемое значение.Вызываемый объект вызывается родительским Mapper во время «инициализации mapper», что происходит только при первом использовании mappers, и предполагается, что это происходит после того, как все отображения были построены.Это может быть использовано для решения порядка объявления и других проблем с зависимостями, например, если Child объявлено ниже Parent в том же файле * [.] *

[...]

При использовании декларативного расширения декларативный инициализатор позволяет передавать строковые аргументы в relationship().Эти строковые аргументы преобразуются в вызываемые объекты, которые оценивают строку как код Python, используя декларативный реестр классов в качестве пространства имен.Это позволяет автоматически выполнять поиск связанных классов через их строковое имя и устраняет необходимость импорта связанных классов вообще в локальное пространство модуля * [.] *

[...]

  • primaryjoin -

    [...]

    primaryjoin также можетпередается как вызываемая функция, которая оценивается во время инициализации преобразователя, и может передаваться как строка, вычисляемая на Python, при использовании декларативного.

[...]

  • вторичное соединение -

    [...]

    secondaryjoin также может бытьпередается как вызываемая функция, которая вычисляется во время инициализации преобразователя, и может передаваться как строка, вычисляемая на Python, при использовании декларативного.

И строка, и лямбда определяютте же самые выражения user_flrs.c.followed_id == User.id / user_flrs.c.follower_id == User.id, которые использовались в первом варианте, но поскольку они заданы как строковые и вызываемые функции, соответственно, вы откладываете eоценка, пока SQLAlchemy не должна завершить эти объявления.

...