如何在 SQLAlchemy 中正确处理这种多对多关系?收到“检测到双向属性冲突”

Jam*_*sis 1 python postgresql sqlalchemy relational-database

我正在代码中设置数据库,但在尝试弄清楚如何按照我想要的方式建立多对多关系时遇到困难。

基本上,我有一个User班级,其中有一个与班级的 m2m Team。事实上,一个团队有很多用户,一个用户可以是多个团队的一部分。到目前为止一切都很好。当我想向团队的用户授予“管理员”权限时,问题就出现了。

我尝试使用关联对象,但我不知道如何将关系标记为管理员。因此,我创建了第二个映射表,将团队映射到用户表中的管理员。如图所示:

Base = declarative_base()

# Many to Many relationship mapping tables
user_team_map = Table(
    'user_team_map', Base.metadata,
    Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
    Column('team_id', Integer, ForeignKey('teams.id'), primary_key=True)
)

admin_team_map = Table(
    'admin_team_map', Base.metadata,
    Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
    Column('team_id', Integer, ForeignKey('teams.id'), primary_key=True)
)

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    first_name = Column(String(24), nullable=False)
    teams = relationship('Team', secondary=user_team_map, back_populates='users')
    admin_of = relationship('Team', secondary=admin_team_map, back_populates='users')


class Team(Base):
    __tablename__ = 'teams'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    users = relationship('User', secondary=user_team_map, back_populates='teams')
    admins = relationship('User', secondary=admin_team_map, back_populates='teams')
Run Code Online (Sandbox Code Playgroud)

当我尝试创建这些表时,我得到: ValueError: Bidirectional attribute conflict detected: Passing object <User at 0x7f2089d7dcc0> to attribute "Team.admins" triggers a modify event on attribute "Team.users" via the backref "User.teams".

我当然不是数据库向导,所以我想这是我的整体方法是不正确的。我应该如何去实现我想要的关系?如果我应该使用关联对象我该怎么做?

Per*_*DBA 5

(资格:我不了解 SQLAlchemy,所以我无法用这些术语回答。我将用Relational-DatabaseSQL术语回答:我确信您可以弄清楚实现。我使用的术语仅是关系型)。

是的,错误消息是有效的,即使错误消息文本可能不是特定的或没有指示您的问题,因为您确实有重复项(在数据库或 ORM 中不可接受)。例如。根据资格,我不会使用术语“*定向”,因为关系在关系数据库或 SQL 中没有方向(它们有父级和子级)。

这个问题有两个层次,我会按逻辑顺序解释。第一个层次是数据库存储,第二个层次是存储转换为类的问题。

1 存储

1.1 关联表

首先需要了解物理层面。尽管它通常是逻辑设计的结果,但似乎存在一个差距:您在没有正式考虑逻辑的情况下就进入了物理。

你拥有的是一个关联表user_team_map。它与 关联usersteams并将 n 对 n 关系解析为两个 0 对 n 关系,每个关系一个到父级。(我也不会使用这个词map。)

联想式

1.2 你的,关系化的

然后你需要确定:

each team has 0-to-n has admins(哪个是users

但是您“无法弄清楚如何将关系标记为管理员”,因此您将整个user_team_map结构复制到admin_team_map.

  • 这是一个严重的标准化错误,它将在逻辑和物理层面上引起问题。因此,SQLAlchemy 对此表示不满也就不足为奇了(尽管错误消息文本并不具有指示性)。其一,它允许一个不在其中的人user成为admin其中的一个。teamusersteam

我们只需在表中将 an“标记”user为 an即可。关联表包含对两个父级的引用,向其中添加一列会将其移出关联表的类别,进入普通表。adminuser_team-map

你的

  • 空值是自杀。它们是由于标准化错误造成的。

1.3 关系解决方案

关系型

  • 请注意,这实现了谓词:
    each team has 0-to-n admins
    而不是谓词:
    each team has 0-to-1 admin
  • MemberName & AdminName角色名称为UserName

1.4 备案系统解决方案(不推荐)

由于“理论家”和“作者”将1960年代的前关系系统宣传和营销为“关系”,在“文献”和“教科书”中,存在很多混乱。生成的 RFS 没有完整性;力量; 或关系系统的速度。无论如何,这就是 ORM 使用的内容,这就是您正在使用的内容,所以我将提供它。

如果遵循这些声称是“关系性”的指示,就会得出这样的结果。

精神错乱

  • 不会阻止关系模型中禁止的重复行
  • 庸人喜欢重复Foreign Key引用,以及由此导致的循环引用问题。

1.5 备案系统解决方案(不推荐)

如果我们只修复这两个错误。

退化的

  • 请注意,每个表中都需要一个附加列和一个附加索引来存储物理指针Record Id
  • 正确的索引(例如 on UserName)不能被删除:如果你这样做[1.4],你将允许重复的行(不是记录),这是被禁止的。
  • 所有其他原始性尚未得到解决
  • 在这个简单的示例中,如果减去Record Ids,则将其提升为关系型 [1.3]。

2级

您使用的类中似乎存在理解和实现错误。这是一个经典的例子,在使用 ORM 时非常常见。当底层存储没有被正确理解或实现时,问题就会变得更加复杂。

我相信你会实现[1.5]:

班级

  • 为了保持理智和易于实施(防止可预防的错误),基类必须与数据库中的存储相匹配:这些是原子的,因为您需要对它们进行 CRUD
  • 对于同时需要原子类和多个值(列表)的窗口,您需要一个单独的只读类(您不应通过这些对象更新数据库)。

多一个

如果您想了解有关关系的更多信息,以及它们如何在 SQL 级别(承载逻辑级别的所有可能性)实现,请阅读此答案(仅限第一部分)。