SQLAlchemy 附加实例错误与常规属性(非关系)
我刚开始使用SQLAlchemy,遇到了一个叫做DetachedInstanceError的错误,但在网上找不到太多相关信息。我是在会话外使用这个实例,所以SQLAlchemy无法加载任何关系是很正常的,前提是这些关系没有被提前加载。不过,我访问的属性并不是一个关系,实际上这个对象根本没有任何关系。我找到了一些解决方案,比如急切加载(eager loading),但我无法应用,因为这不是一个关系。我甚至尝试在关闭会话之前“触碰”这个属性,但这仍然无法防止这个异常。为什么一个非关系属性在之前成功访问过后,仍然会导致这个异常呢?如果有人能帮我调试这个问题,我会非常感激。同时,我会尝试创建一个可以复现这个问题的独立场景,并在这里更新。
更新:这是实际的异常信息和一些堆栈信息:
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 159, in __get__
return self.impl.get(instance_state(instance), instance_dict(instance))
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
value = callable_(passive=passive)
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/state.py", line 280, in __call__
self.manager.deferred_scalar_loader(self, toload)
File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/mapper.py", line 2323, in _load_scalar_attributes
(state_str(state)))
DetachedInstanceError: Instance <ReportingJob at 0xa41cd8c> is not bound to a Session; attribute refresh operation cannot proceed
部分模型看起来是这样的:
metadata = MetaData()
ModelBase = declarative_base(metadata=metadata)
class ReportingJob(ModelBase):
__tablename__ = 'reporting_job'
job_id = Column(BigInteger, Sequence('job_id_sequence'), primary_key=True)
client_id = Column(BigInteger, nullable=True)
而字段client_id是导致这个异常的原因,使用方式如下:
查询:
jobs = session \
.query(ReportingJob) \
.filter(ReportingJob.job_id == job_id) \
.all()
if jobs:
# FIXME(Hari): Workaround for the attribute getting lazy-loaded.
jobs[0].client_id
return jobs[0]
这就是在会话范围外触发异常的原因:
msg = msg + ", client_id: %s" % job.client_id
8 个回答
我遇到过类似的问题,错误信息是 DetachedInstanceError: Instance <> is not bound to a Session;
。
情况其实很简单,我把会话和要更新的记录传给我的函数,然后它会把记录合并并提交到数据库。在第一个例子中,我遇到了这个错误,因为我太懒了,以为只要返回合并后的对象,就能更新我的操作记录(也就是说,它的 is_modified
值会变成 false)。确实返回了更新后的记录,is_modified
现在是 false,但后续使用时又出现了错误。我觉得这可能是因为有相关的子记录,但我不太确定。
def EditStaff(self, session, record):
try:
r = session.merge(record)
session.commit()
return r
except:
return False
经过大量的搜索和阅读关于会话的资料,我意识到,由于我在提交之前就捕获了实例 r 并返回了它,当同一条记录再次被发送回这个函数进行编辑/提交时,它已经失去了会话。
所以为了解决这个问题,我只需查询数据库中刚刚更新的记录,并返回它以保持在会话中,同时把它的 is_modified
值重新标记为 false。
def EditStaff(self, session, record):
try:
session.merge(record)
session.commit()
r = self.GetStaff(session, record)
return r
except:
return False
设置 expire_on_commit=False
也避免了上面提到的错误,但我认为这并没有真正解决问题,可能会导致其他很多问题。
我们也遇到了类似的错误,即使把 expire_on_commit
设置为 False
。最后发现问题其实是因为有两个 sessionmaker
,它们在不同的请求中都被用来创建会话。我不是很明白具体发生了什么,但如果你在 expire_on_commit=False
的情况下看到这个异常,记得检查一下你是不是初始化了两个 sessionmaker
。
我在试图找出导致异常的代码时,发现了问题的根本原因。我把同样的属性访问代码放在了不同的位置,在关闭会话后测试,发现关闭查询会话后立刻使用这些代码并没有问题。问题出现在关闭一个新打开的会话后,这个会话是用来更新对象的。一旦我明白了会话关闭后对象的状态就不能用了,我找到了一条讨论这个问题的帖子。帖子里提到的两个解决方案是:
- 保持会话处于打开状态(这很明显)
- 在调用
sessionmaker()
时指定expire_on_commit=False
。
第三个选项是在会话创建后手动将expire_on_commit
设置为False
,可以这样写:session.expire_on_commit = False
。我验证过,这样做解决了我的问题。