后期绑定多对多自指关系的语法

时间:2019-02-26 15:09:06

标签: python sqlalchemy many-to-many self-reference

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

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

但是在我发现的每个示例中,用于定义关系中的primaryjoinsecondaryjoin的语法都是早期绑定的语法:

# 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所示

我的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移到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,找不到任何涉及本地映射外键列的简单相等表达式。确保引用列与ForeignKeyForeignKeyConstraint关联,或在联接条件中使用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,找不到任何涉及本地映射外键列的简单相等表达式。确保引用列与ForeignKeyForeignKeyConstraint关联,或在联接条件中使用foreign()注释进行注释。要允许'=='以外的比较运算符,可以将该关系标记为viewonly=True

1 个答案:

答案 0 :(得分:1)

不能在联接过滤器中使用id,不,因为那是built-in id() function,而不是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() documentation

  

relationship()接受的某些参数可选地接受可调用函数,该函数在被调用时会产生所需的值。父Mapper在“映射器初始化”时调用可调用对象,这仅在首次使用映射器时发生,并假定在所有映射都已构建之后。这可用于解决声明顺序和其他依赖项问题,例如如果Child在同一文件中的Parent下方声明* [。] *

     

[...]

     

使用声明式扩展名时,声明式初始化程序允许将字符串参数传递给relationship()。这些字符串参数使用Declarative class-registry作为名称空间,转换为可调用的字符串,将字符串评估为Python代码。这样可以通过字符串的名称自动查找相关的类,并且完全不需要将相关的类导入本地模块空间* [。] *

     

[...]

     
      
  • 主连接

         

    [...]

         

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

  •   
     

[...]

     
      
  • 次要连接

         

    [...]

         

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

  •   

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