SQLAlchemy,对象刷新时预先加载

die*_*erg 5 python orm sqlalchemy eager-loading

如果 SQLAlchemy 中有以下 ORM 设置:

class Foo(Base):
    id = Column(Integer, primary_key=True)
    status = Column(String)
    barId = Column(Integer, ForeignKey("bar.id"))
    bar = relationship("Bar", lazy="joined")

class Bar(Base):
   id = Column(Integer, primary_key=True)
Run Code Online (Sandbox Code Playgroud)

因此,我希望始终为每个 Foo 对象提供关联的 Bar 对象。我经常将 Foo 对象从会话中分离出来,并继续使用它的值和 Bar 的值。我有时需要更新 Foo 的状态字段。在这种情况下,我创建一个新会话,将 foo 对象添加到会话中并提交它。提交后,与 Foo 对象关联的 Bar 对象将失效,但不会通过提交对 Foo 对象的隐式刷新来重新加载。再次从会话中分离 Foo 对象后,Bar 对象不再可用。我发现解决这个问题的唯一方法是在提交 foo 后显式地预先加载 bar 对象。

工作流程示例:

session = Session()
foo = session.query(Foo).get(id) <-- foo.bar is automatically eager loaded
session.close()
....
session = Session()
session.add(foo)
foo.status = 'done'
session.commit()       <-- foo is commited and refreshed, foo.bar is not
session.refresh(foo)   <-- same here, foo.bar is not loaded
#foo.bar               <-- only explicit eager loading foo.bar here works
session.close()
....
foo.bar                <-- error if not explicitly eager loaded
Run Code Online (Sandbox Code Playgroud)

我想将此设置用于一些类似条形的小对象。要求我记住始终显式地重新加载 foo.bar 对象很容易出错。所以我的问题是:我可以在所有情况下急切加载 foo.bar 吗?无论是 query()、commit()(隐式刷新)还是(显式)refresh()?

zzz*_*eek 6

首先,“commit()”不是“刷新”——它实际上会使所有数据过期,因此您会看到所有映射的属性不再存在于foo.__dict__. 当您再次触摸这些属性时,会发生隐式刷新。对于那些在提交后不需要跨事务同步的应用程序来说,简单地expire_on_commit=False在 a 中设置是一种非常常见的做法,因此这可能是最隐式的工作流程。Session

接下来,将使用配置的急切加载器session.refresh(foo)进行加载。bar不知道为什么你会看到foo.bar未加载,我查了一下,这个功能至少可以追溯到 0.5 版本。一个简单的测试证实了这一点:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    status = Column(String)
    barId = Column(Integer, ForeignKey("bar.id"))
    bar = relationship("Bar", lazy="joined")

class Bar(Base):
    __tablename__ = 'bar'
    id = Column(Integer, primary_key=True)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

s.add(Foo(id=1, bar=Bar()))
s.commit()

f1 = s.query(Foo).get(1)
f1.status = 'done'
s.commit()

assert 'bar' not in f1.__dict__
s.refresh(f1)
assert 'bar' in f1.__dict__
s.close()

assert f1.bar.id == 1
Run Code Online (Sandbox Code Playgroud)

接下来,SQLAlchemy 不鼓励使用处于“分离”状态的对象,一般原因是您的映射对象代表正在进行的数据库事务的代理。这就是为什么当交易结束时,所有数据都会过期。就我个人而言,我认为对象需要在分离状态下使用通常没有正当理由;分离主要是为了将对象传输到其他会话,将它们存储在缓存中,诸如此类。但我们确实有很多用户在任何情况下都依赖独立的使用模式,而且我已经确保我可以在合理的程度上支持他们,所以我不会对此太担心。