SQLAlchemy,获取未绑定到会话的对象

100

我正在尝试从数据库获取一个对象集合,并将其传递给另一个未连接到数据库的进程。我的代码如下,但是我一直收到以下错误:

sqlalchemy.exc.UnboundExecutionError: Instance <MyClass at 0x8db7fec> is not bound to a Session; attribute refresh operation cannot proceed

get_list()方法之外尝试查看我的列表元素时出现问题。

def get_list (obj):
    sesson = Session()
    lst = session.query(MyClass).all()
    session.close()
    return lst

然而,若我使用以下代码:

def get_list_bis (obj)
    session = Session()
    return session.query(MyClass).all()

我能使用这些元素,但我担心会话状态没有被关闭。

我在这里漏掉了什么?


1
我相当确定原始代码中还有更多内容,除非SQLA自从编写代码以来发生了变化,因为仅关闭会话将清除剩余对象并过期属性。另一方面,提交或回滚确实会过期属性。 - Ilja Everilä
@IljaEverilä 这是近8年前的问题。我非常确定SQLAlchemy在那段时间内发生了变化。 - Sardathrion - against SE abuse
只是为了确保,我在0.5(11岁)上进行了测试,但无法复现。即使在那时候,关闭也不会过期属性,并且如已接受答案的评论中所指出的那样,Session.close()隐式地执行(并且当时也是如此)expunge_all() - Ilja Everilä
但是,也许发生的情况是在关闭会话后访问了一个惰性加载的“关系”属性,这将导致上述错误。这就是为什么在 SQLA 问题中,如果相关模型是问题的一部分,那么它们会有所帮助。 - Ilja Everilä
@IljaEverilä 这是可能的。但是我不确定我是否还有那段代码。就像我说的,那是几年前的事了。☹ - Sardathrion - against SE abuse
3个回答

97
如果您希望查询会话产生的一系列对象在会话范围之外可用,您需要将它们从会话中清除(expunge)出来。
在您的第一个函数示例中,您需要添加一行代码:
session.expunge_all()

之前

session.close()

更普遍地说,假设会话没有立即关闭,就像在第一个例子中一样。也许这是一个在整个Web请求的持续时间内保持活动状态的会话或类似的东西。在这种情况下,你不想进行expunge_all操作。你需要更加精准:

for item in lst:
    session.expunge(item)

31
但是......但是......"close()方法会发出expunge_all(),并释放任何事务/连接资源。" 这个陈述在您参考的页面上,至少可以追溯到0.6版本。 - Oddthinking
12
正如上面的评论所指出的那样,这个答案并不完全正确。仅仅关闭一个会话应该会使属性过期但仍然可用。Session.commit()Session.rollback()会使ORM加载的状态过期,在默认配置下被清除,而在提交之前将其删除可以防止过期,使属性保留在它们的过期状态。 - Ilja Everilä
7
我发现这也没有帮助。我尝试了清除和关闭,但仍然没用。虽然我知道它已经过期且未连接到数据库,但我仍想查找并关闭db会话以快速将连接返回到池中,并继续使用“过期”的对象,以便将其作为JSON返回给API响应。很奇怪这必须抛出异常。 - bjm88

48

这种情况通常发生在对象处于expired状态时,例如在提交后,对象会变为expired状态,然后当这些expired对象即将被使用时,ORM会尝试对它们进行refresh,但是当对象从会话中detached(例如因为该会话已经closed)时,这是不可能完成的。通过创建带有expire_on_commit=False参数的会话,可以管理此行为。

>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.expired
True  # then it will be refreshed...

这对我有用:session_maker = sessionmaker(bind=engine, expire_on_commit=False)。但在使用之前,值得阅读 https://groups.google.com/g/sqlalchemy/c/uYIawg4SUQQ。 - Ravindra

3

在我的情况下,我也同时保存了相关实体,并且这个技巧帮助我使用Session可迭代的特性,刷新了所有在此会话中的实例:

map(session.refresh, iter(session))  # call refresh() on every instance

这种方法极其低效,但是能够发挥作用。在单元测试中应该没问题。
最后要注意的是,在Python3中,map()是一个生成器,不会实际执行操作。应该使用真正的循环或列表推导式。

8
如果你不关心结果,无论如何都不应该使用map函数。 - ThiefMaster

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