如何在复杂的外键关系中正确继承SQLAlchemy类?

0 投票
1 回答
2354 浏览
提问于 2025-04-17 07:45

我实现了一个叫做“连接表继承”的功能,具体内容可以参考这个问题:SQLAlchemy 继承

现在有这样的情况:我想要有一个

  • 用户,他有一个 emailaddress(电子邮件地址)
  • 别名,它将一个别名 emailaddress 指定给另一个电子邮件地址

为了确保 emailaddress 是唯一的,我的想法是让这两个类都继承自 Emailaddress,使用 连接表继承。这个例子实现了以下几个类:

  • Emailaddress
  • EmailaddressUser(Emailaddress)
  • EmailaddressAlias(Emailaddress)

这种继承方式使得使用起来更方便:

u = EmailaddressUser(name="Testuser", emailaddress="testuser@test.com")

=> 我不需要事先创建一个 Emailaddress 的实例,这样使用起来更简单。

不过,EmailaddressAlias 的情况就不一样了,尽管它唯一的不同是第二个属性是指向同一个 emailaddress 的外键。因此,我需要指定 inherit_condition。但是:

  a = EmailaddressAlias (
      real_emailaddress="testuser@test.com",
      alias_emailaddress="tu@test.com"
      )

--> 在将其添加到数据库时会抛出一个完整性错误。完整的例子可以在这里查看:

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Emailaddress(Base):
  __tablename__ = 'emailaddresses'
  emailaddress = sa.Column(sa.String, primary_key=True)
  emailtype = sa.Column(sa.String, nullable=False)
  __mapper_args__ = {'polymorphic_on': emailtype}


class EmailaddressUser(Emailaddress):
  __tablename__ = 'emailaddress_users'
  __mapper_args__ = {'polymorphic_identity': 'user'}
  emailaddress = sa.Column(
      sa.String,
      sa.ForeignKey('emailaddresses.emailaddress'),
      primary_key=True)
  name = sa.Column(sa.String, nullable=False)


class EmailaddressAlias(Emailaddress):
  __tablename__ = 'emailaddresses_alias'
  alias_emailaddress = sa.Column(
      sa.String,
      sa.ForeignKey('emailaddresses.emailaddress'),
      primary_key=True)
  real_emailaddress = sa.Column(
      sa.ForeignKey('emailaddresses.emailaddress'),
      nullable=False)
  __mapper_args__ = {
      'polymorphic_identity': 'alias',
      'inherit_condition':Emailaddress.emailaddress==alias_emailaddress}


if __name__ == '__main__':
  engine = sa.create_engine ('sqlite:///email.sqlite', echo=True)
  Base.metadata.bind = engine
  Base.metadata.create_all ()
  Session = orm.sessionmaker (engine)
  session = Session ()
  # add user (works):
  u = EmailaddressUser(name="Testuser", emailaddress="testuser@test.com")
  session.add(u)
  session.commit()
  # --> INSERT INTO emailaddresses (emailaddress, emailtype) VALUES (?, ?)
  # --> ('testuser@test.com', 'user')
  # 'emailaddress' is inserted correctly

  # add alias (throws an IntegrityError):
  a = EmailaddressAlias (
      real_emailaddress="testuser@test.com",
      alias_emailaddress="tu@test.com"
      )
  session.add(a)
  session.commit()
  # --> INSERT INTO emailaddresses (emailtype) VALUES (?)' ('alias',)
  # 'emailaddress' is missing! => IntegrityError

1 个回答

2

看起来可能有个问题,就是 EmailaddressAlias 是从 EmailAddress 继承的,而不是从 Base 继承的。

我不是这方面的专家,也没有通过 mapper_args 来做过这个,但我发现使用 relationship() 来设置外键效果挺好的。例如:

from sqlalchemy.orm import relationship
class EmailaddressAlias(Base):
...
    alias_emailaddress_fk = sa.Column(sa.String, sa.ForeignKey('emailaddresses.emailaddress'))
    alias_emailaddress = relationship(EmailAddress, primaryjoin=alias_emaladdress_fk==EmailAddress.emailaddress)
    real_emailaddress_fk = sa.Column(sa.String, ForeignKey('emailaddresses.emailaddress'))
    real_emailaddress = relationship(EmailAddress,primaryjoin=real_emailaddress_fk==EmailAddress.emailaddress)

撰写回答