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

问题描述 投票:0回答:1

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

以下是三个例子,一个来自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

python sqlalchemy many-to-many self-reference
1个回答
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. 将关系定义为callables;这些在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() documentation

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

[...]

使用Declarative扩展时,Declarative初始值设定项允许将字符串参数传递给relationship()。这些字符串参数转换为callables,使用Declarative类注册表作为命名空间,将字符串计算为Python代码。这允许通过字符串名称自动查找相关类,并且无需将相关类导入到本地模块空间* [。] *

[...]

  • primaryjoin - [...] primaryjoin也可以作为可调用函数传递,该函数在mapper初始化时计算,并且在使用Declarative时可以作为Python可评估字符串传递。

[...]

  • 次级连接 - [...] secondaryjoin也可以作为可调用函数传递,该函数在mapper初始化时计算,并且在使用Declarative时可以作为Python可评估字符串传递。

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

© www.soinside.com 2019 - 2024. All rights reserved.