【问题标题】:How does a sqlalchemy object get detached?sqlalchemy 对象如何分离?
【发布时间】:2013-11-06 16:59:24
【问题描述】:

我的代码结构如下:

project
--app
----utils
------util.py
----__init__.py
----models.py
--tests
----__init__.py

在测试/__init__.py 中,我有代码通过从 app/__init__.py 导入所有代码来初始化应用程序(烧瓶,如果这很重要)和数据库会话。我可以在 tests/__init__.py 中创建模型、查询和访问 backrefs 的实例。以下形式的代码可以正常工作:

objs = SomeModel.query.all()
for o in objs:
    o.backref

但是,如果我这样做:

from utils.util import some_function
objs = SomeModel.query.all()
for o in objs:
    some_function(o)

其中 some_function 只是访问一个反向引用

def some_function(obj):
    obj.backref

我收到类似DetachedInstanceError: Parent instance <SomeModel at 0x2c1fe10> is not bound to a Session; lazy load operation of attribute 'backref' cannot proceed的错误

阅读 sqlalchemy 文档表明我需要将对象重新关联到数据库会话。我这样做了,看起来它可以工作(即运行该函数不会因先前的错误而失败):

import db_session
def some_function(obj):
    db_session.add(obj)
    obj.backref

那么,一个对象究竟是什么时候分离的呢?似乎只是将对象传递给另一个模块中的函数就会将其与会话分离。对象是否不知道与其关联的 sqlalchemy 会话?我试图避免使用db_session.add(obj),这似乎是很多样板代码。

【问题讨论】:

  • 我无法在类似的代码上得到相同的结果。您能否提供最少的代码来重现您遇到的相同错误错误?
  • some_function 函数是一个同步 celery 任务。很抱歉没有包括在内。当我用最少的代码重现问题时,我会进一步更新问题。

标签: python sqlalchemy flask flask-sqlalchemy


【解决方案1】:

我在处理我自己的similar question 关于属性过期和实例分离的问题时遇到了这个问题。 univerio 给了我一个很好的答案,根据我一直在学习的内容,我或许能够对你的问题有所了解。

在我的情况下,我正在创建、提交或回滚,然后在单个 with...as... 子句的范围内关闭 Session,然后尝试访问我保存的实例(您的示例中的 obj ) 紧随其后但超出了with 子句的范围。发生的事情是 Session 在我尝试引用保存的对象之前已关闭。 默认情况下,在 SQLAlchemy 中,如果没有 active Session,则无法访问持久的属性/对象,除非明确告知允许它。这是为了“保护”代码免于意外或在不知不觉中使用过时/不正确的数据,方法是强制应用程序首先查询/检索更新的数据,这需要关联的Session。因此,在我的情况下,在提交后将 Session 保持打开状态意味着对象可以使用 Session 来查询数据库,以防记录在首次写入后被修改。

在您的情况下,用于通过objs = SomeModel.query.all() 获取对象的Session 在查询之后但在调用obj.backref 之前被关闭或断开连接(虽然我不确定如何;我不知道SomeModel 是什么,确切地说,我假设是来自 Flask 的构造,它在其背景中包含 Session)。所以obj 不再有与数据库的连接,因此是“分离的”。通过将其添加到db_session,您允许obj 重新建立与其源数据库的连接,通过它可以查询以检查更新的属性,因此它不再分离。

最后,值得一提的是,DetachedInstanceError 可以通过指定与obj 关联的原始Session 避免自动expire 属性。如果obj过期,则不会引发错误,obj 仍会分离,这意味着当您调用obj.backref 返回的值可能不正确/过时。您在问题中询问了分离,但到期是一个相关但不相同的概念。

除此之外——如何设置obj 不会过期:要么在Session 的初始化时

my_session = sessionmaker(expire_on_commit=False

sessionmaker的初始化

my_sessionmaker = sqlalchemy.orm.sessionmaker(expire_on_commit=False)

甚至在 Session 已经实例化之后

my_session.expire_on_commit = False

【讨论】:

  • 很好的答案。 Session.add(detachedInstance) 帮助我在我的 Flask py.test 运行中摆脱了这些恼人的错误。我只想指出,使用 expire_on_commit=False 定义 sessionmaker 虽然乍一看很好,但确实会对数据库持久性产生副作用,在我的情况下,这会导致其他更难以修复的错误。例如,相同的代码将提交两次相同的实例,该实例上设置了唯一约束且 expire_on_commit=False。
猜你喜欢
  • 2018-08-12
  • 1970-01-01
  • 2016-09-28
  • 2020-02-20
  • 1970-01-01
  • 1970-01-01
  • 2021-09-15
  • 2018-01-25
  • 1970-01-01
相关资源
最近更新 更多