DetachedInstanceError:实例<TCustomer在0x428ddf0>未绑定到会话
我看过之前一些人遇到类似问题的例子,但我还是搞不清楚为什么我的代码不行,因为我用的是同一个会话。
这是我的一段代码,它简单地查询数据库,看看客户是否存在,然后再用这个客户进行另一个插入操作。但是一到执行 transaction.commit()
这一行,就会抛出 DetachedInstanceError
的错误。这个错误会不会是因为我在其他方法里之前执行过 transaction.commit()
呢?
@view_config(route_name='create_location', renderer='templates/main.html')
def create_location(request):
#get the json data from the ajax call
data = request.json_body
session = DBSession
locationName = data["location"]
custID = data["custID"]
Customer = session.query(TCustomer).filter(TCustomer.ixCustomer==custID).one()
#create customer
Location = TLocation(sDescription=locationName, ixCustomer=Customer)
session.add(Location)
session.flush()
transaction.commit()
return Response('ok')
这是我的数据库会话:
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
2 个回答
这可能是因为在另一个方法中之前调用了 transaction.commit() 吗?
是的,如果那个方法是在同一个请求-响应周期内被调用的。一旦事务被提交,SQLAlchemy 就不能保证内存中的 ORM 对象仍然代表数据库的实际状态。所以你不能在一个事务中获取一个对象,然后在另一个事务中保存它,而不明确地将现在已经分离的对象合并到一个新的会话中。
一般来说,你的代码中不应该使用 transaction.commit()
。 使用 ZopeTransactionExtension
的目的是将 SQLAlchemy 的事务与 Pyramid 的请求-响应周期绑定在一起——当请求开始时会创建一个新的会话,如果请求成功则提交,如果请求失败(也就是在你的视图中抛出异常)则回滚。在你的代码中,你不需要担心提交任何东西——只需将你的新对象添加到会话中:
@view_config(route_name='create_location', renderer='templates/main.html')
def create_location(request):
#get the json data from the ajax call
data = request.json_body
customer = DBSession.query(Customer).filter(Customer.id==data["custID"]).one()
session.add(Location(description=data["location"], customer=customer))
return Response('ok')
(我忍不住让代码看起来更像“正常”的 Python 代码……匈牙利命名法……呃……现在不太常用了……感谢你让我回忆起这些 :))) 详情请见 PEP 8)。
在少数情况下,你可能希望请求的某部分无论如何都能成功,或者仅在发生错误时才将数据保存到数据库(例如,将错误记录到数据库)。在这些情况下,你需要使用一个没有 ZopeTransactionExtension 配置的单独会话。你需要手动提交这样的会话:
try:
do_something_which_might_fail()
except Exception as e:
session = ErrorLogSession()
session.add(SomeORMObject(message=format_exception(e))
session.commit()
进一步阅读:
我什么时候构建会话,什么时候提交它,什么时候关闭它? - SQLAlchemy 文档中的高层概述。“简而言之:一般来说,保持会话的生命周期与访问和/或操作数据库数据的函数和对象分开。”请注意,Pyramid 已经为你处理了所有的会话管理——与代码分开并在外部进行。
使用 SQLAlchemy 的数据库 - 一个基本的 Pyramid 应用程序,使用 SQLAlchemy。查看
dev wikipage_add()
函数,了解保存数据到数据库的典型代码示例。
这里是解决方案:
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension(keep_session=True)))
我需要加上 keep_session=True
这个设置。
然后,我不需要再写 session = DBSession()
,只要直接用 DBSession
来进行查询和处理会话就可以了。