Sqlalchemy真的有一对一关系吗?

4
我有以下语义。警报可以有状态变化,但只能有一个。 状态变化只能有一个警报。一个状态变化可以有一个原因,同时一个原因可以存在于多个状态变化中。 我尝试了以下模式。
class Alert(BaseDb):
    __tablename__ = 'alerts'
    __table_args__ = (
        PrimaryKeyConstraint('id', name='pk_alerts'),
    )

    id = Column(Integer)
    alert_text = Column(Text)

class AlertStateChange(BaseDb):
    __tablename__ = 'alert_state_change'
    __table_args__ = (
        PrimaryKeyConstraint('id', name='pk_alert_state_change'),
        ForeignKeyConstraint(
            ['reason_id'],
            ['reasons.id'],
            name='fk_alert_status_change_reason_id__reasons'
        ),

        ForeignKeyConstraint(
            ['alert_id'],
            ['alerts.id'],
            name='fk_alert_status_change_alert_id__alert'
        ),


    )

    id = Column(Integer)
    reason_id = Column(Integer)
    alert_id = Column(Integer)
    reason = relationship('Reason', backref='status')
    alert = relationship('Alert',
                         backref=backref('status', uselist=False))
    status = Column(Text)

但SQLAlchemy允许我为相同的警报(相同的alert_id)添加AlertStateChange对象。它会像通常一样提交并生成新的id。当将两个相同警报的AlertStatusChange对象放入数据库后,尝试以下操作:

alert.status

会给我以下警告

SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'Alert.status' % self.parent_property)

返回的对象是添加的第一个AlertStateChange对象。第二个在数据库中但被忽略了。这不是真正的一对一关系。应该会抛出异常吗?我应该将alert_id添加为主键或唯一值,对吗?


你是正确的,如果想要确定的话,你应该在数据库层面上强制执行一对一的关系,可以使用警报ID作为主键/外键,或者在其上设置唯一约束。 - Ilja Everilä
请注意,SQLA关系是ORM概念,仅映射到DB中的模式,或者这是我从http://docs.sqlalchemy.org/en/latest/orm/tutorial.html中理解的。 - Ilja Everilä
1个回答

3

执行以下操作:

  • 在关系上设置uselist=False
  • 在子表中设置引用列的unique=True
  • 您还可以在子表上设置nullable=False
  • 您还可以为父对象添加自定义__init__以实现严格的一对一关系

现在它将正常工作。

class Parent(Base):
    __tablename__ = 'parents'

    id = Column(Integer, primary_key=True)
    Child = relationship("Child", uselist=False, backref="Parent")

    def __init__(self,**kwargs):
        if 'Child' not in kwargs:
            raise RuntimeError('Need value')
        ...

class Child(Base):
    __tablename__ = 'childs'

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parents.id'), unique=True)    

Base.metadata.create_all(engine)
session = Session(bind=engine)

ch1 = Child(Parent=Parent())
session.add(ch1)

p1 = Parent(Child=Child())
session.add(p1)

session.commit()

for row in session.query(Parent):
    print row.Child.id
for row in session.query(Child):
    print row.Parent.id

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接