首页 > 解决方案 > 后期绑定多对多自引用关系的语法

问题描述

我发现了很多关于如何使用单独的表或类创建自我引用的多对多关系(对于用户关注者或朋友)的解释:

以下是三个示例,其中一个来自 Mike Bayer 本人:

但是在我发现的每个示例中,在关系中定义primaryjoinand的语法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所有对象的列时,如Mixins: Augmenting the base from the docs

我的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但是现在在我将's 移到 mixin之前,我一直在忠实地为我服务了一段时间的追随者关系遇到了麻烦:

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

如果我将括号更改为引号以利用后期绑定,它会引发相同的错误。我不知道如何用 and 注释这个东西,foreign()因为remote()我根本不知道 sqlalchemy 希望我在跨辅助表的自引用关系上将什么描述为外部和远程!我已经尝试了很多这种组合,但到目前为止还没有奏效。

我有一个非常相似(尽管不相同)的问题,即不跨越单独表的自引用关系,关键是将remote_side参数转换为后期绑定的。这对我来说很有意义,因为id在早期绑定过程中该列不存在。

如果我遇到问题的不是后期绑定,请告知。但是,在当前范围内,我的理解是id映射到 Python 内置id(),因此不能作为早期绑定关系。

在连接中转换idBase.id会导致以下错误:

ArgumentError: 找不到任何简单的等式表达式,涉及'user_flrs.follower_id = "<name unknown>"'关系上的主连接条件的本地映射外键列User.followed_users。确保引用列与ForeignKey或相关联ForeignKeyConstraint,或者在连接条件中使用注释进行foreign()注释。要允许比较运算符以外'=='的,可以将关系标记为viewonly=True

标签: pythonsqlalchemymany-to-manyself-reference

解决方案


您不能id在联接过滤器中使用,不,因为那是内置id()函数,而不是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. 将关系定义为可调用对象;这些在映射器配置时调用以生成最终对象:

    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()接受一个可调用函数,该函数在调用时会产生所需的值。父映射器在“映射器初始化”时调用可调用对象,这仅在第一次使用映射器时发生,并且假定在所有映射都已构建之后。这可用于解决声明顺序和其他依赖问题,例如 ifChild在下面Parent的同一文件中声明*[.]*

[...]

使用声明式扩展时,声明式初始化程序允许将字符串参数传递给relationship(). 这些字符串参数被转换为可调用函数,将字符串评估为 Python 代码,使用声明性类注册表作为命名空间。这允许通过其字符串名称自动查找相关类,并且完全无需将相关类导入本地模块空间*[.]*

[...]

  • 主要加入-

    [...]

    primaryjoin也可以作为可调用函数传递,该函数在映射器初始化时进行评估,并且可以在使用声明时作为 Python 可评估字符串传递。

[...]

  • 辅助连接–</p>

    [...]

    secondaryjoin也可以作为可调用函数传递,该函数在映射器初始化时进行评估,并且可以在使用声明时作为 Python 可评估字符串传递。

字符串和 lambda 都定义了与第一个选项中使用的user_flrs.c.followed_id == User.id/user_flrs.c.follower_id == User.id表达式相同的表达式,但是因为它们分别作为字符串和可调用函数给出,所以您推迟评估直到 SQLAlchemy 需要完成这些声明。


推荐阅读