我可以让SQLAlchemy根据当前的外键值填充关系吗?

Nic*_*ack 6 python sqlalchemy

这是一些代码:

# latest version at https://gist.github.com/nickretallack/11059102

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    def __str__(self):
        return self.name

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey(Parent.id), nullable=False)
    name = Column(String, nullable=False)

    parent = relationship(Parent)

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)

def run():
    # Basic Setup
    Base.metadata.create_all(engine)
    session = Session()
    fred = Parent(name="Fred", id=1)
    george = Parent(name="George", id=2)
    session.add(fred, george)
    session.commit()

    # The test
    bob = Child(name="Bob", parent_id=1)
    print bob.parent, ": Out of session. Should be Fred but is None.\n"

    session.add(bob)
    print bob.parent, ": In session.  Should be Fred but is None.\n"

    session.commit()
    print bob.parent, ": Committed.  Is Fred.\n" 

    bob.parent_id = 2
    print bob.parent, ": Dirty.  Should be George but is Fred.\n"

    session.add(bob)
    print bob.parent, ": Added to session.  Should be George but is Fred.\n"

    session.expire(bob,['parent'])
    print bob.parent, ": Expired.  Should be George but is None?  Wtf?\n"

    session.commit()
    print bob.parent, ": Committed again.  Is None.  Ugh.\n"

if __name__ == '__main__':
    run()
Run Code Online (Sandbox Code Playgroud)

此示例演示了简单地设置关系所依赖的外键字段永远不足以使该关系查询正确的事物.无论我做什么,这都会发生.

是否有可能让sqlalchemy根据当前的外键值填充关系,而不是先保留记录?我可以做些什么让它运行查询吗?

在处理Web表单时,这个问题出现了很多.表单帖子只包含事物的ID,因此处理帖子的最简单方法是在记录中设置ID字段并尝试提交它,如果引用的项目不存在,或者如果存在,则让事务失败另一个问题,只有数据库可以真正了解而不会有竞争条件的风险,例如唯一的约束违规.一旦事务失败,您可能希望将表单重新显示给用户.不幸的是,这些关系都不再正确.

这可能是也可能不是问题,但在我的情况下,这是非常令人沮丧的.为了纠正这些关系,我需要复制这些关系中的逻辑,因为我找不到告诉他们只做查询的方法.

zzz*_*eek 9

  1. 你的"添加"电话是错误的:

    session.add_all([fred, george])
    
    Run Code Online (Sandbox Code Playgroud)
  2. 对于一个甚至不在Session中的完全瞬态对象(我认为它不是一个用例),请使用enable_relationship_loading:

    # The test
    bob = Child(name="Bob", parent_id=1)
    session.enable_relationship_loading(bob)
    print bob.parent, ": Out of session. Should be Fred but is None.\n"
    
    Run Code Online (Sandbox Code Playgroud)
  3. 对于要挂载其关系的挂起对象(也是我不同意的用例,请参阅我将实例上的"foo_id"属性设置为"7",但是"foo"属性仍然是None - 不应该加载Foo使用id#7?)使用load_on_pending标志:

    class Child(Base):
        __tablename__ = 'child'
        id = Column(Integer, primary_key=True)
        parent_id = Column(ForeignKey(Parent.id), nullable=False)
        name = Column(String, nullable=False)
    
        parent = relationship(Parent, load_on_pending=True)
    
    Run Code Online (Sandbox Code Playgroud)
  4. 当您将'parent_id'更改为某些内容时重新加载'parent',如FAQ条目所述,请使用expire:

    session.expire(bob, ['parent'])
    bob.parent_id = 2
    print bob.parent, ": Dirty.  Should be George but is Fred.\n"
    
    Run Code Online (Sandbox Code Playgroud)

脚本完全工作:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    def __str__(self):
        return self.name

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey(Parent.id), nullable=False)
    name = Column(String, nullable=False)

    parent = relationship(Parent, load_on_pending=True)

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)

def run():
    # Basic Setup
    Base.metadata.create_all(engine)
    session = Session()
    fred = Parent(name="Fred", id=1)
    george = Parent(name="George", id=2)
    session.add_all([fred, george])
    session.commit()

    # The test
    bob = Child(name="Bob", parent_id=1)
    session.enable_relationship_loading(bob)
    print bob.parent, ": Out of session. Should be Fred but is None.\n"

    session.add(bob)
    print bob.parent, ": In session.  Should be Fred but is None.\n"

    session.commit()
    print bob.parent, ": Committed.  Is Fred.\n"

    session.expire(bob, ['parent'])
    bob.parent_id = 2
    print bob.parent, ": Dirty.  Should be George but is Fred.\n"

    session.add(bob)
    print bob.parent, ": Added to session.  Should be George but is Fred.\n"

    session.expire(bob,['parent'])
    print bob.parent, ": Expired.  Should be George but is None?  Wtf?\n"

    session.commit()
    print bob.parent, ": Committed again.  Is None.  Ugh.\n"

if __name__ == '__main__':
    run()
Run Code Online (Sandbox Code Playgroud)