我发现了很多关于如何使用单独的表或类创建自引用多对多关系(针对用户关注者或朋友)的解释:
下面是三个示例,其中一个来自Mike Bayer本人:
但是在我发现的每个示例中,用于定义关系中的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
列定义时,如文档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
,找不到任何涉及本地映射外键列的简单相等表达式。确保引用列与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
。
答案 0 :(得分:1)
不能在联接过滤器中使用id
,不,因为那是built-in id()
function,而不是User.id
列。
您有三个选择:
在创建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'
)
对连接表达式使用字符串。配置映射器时,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'
)
将关系定义为可调用对象;这些在映射器配置时被调用以产生最终对象:
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需要完成这些声明。