DetachedInstanceError:实例<TCustomer在0x428ddf0>未绑定到会话

1 投票
2 回答
1326 浏览
提问于 2025-04-18 08:05

我看过之前一些人遇到类似问题的例子,但我还是搞不清楚为什么我的代码不行,因为我用的是同一个会话。

这是我的一段代码,它简单地查询数据库,看看客户是否存在,然后再用这个客户进行另一个插入操作。但是一到执行 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 个回答

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()

进一步阅读:

0

这里是解决方案:

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension(keep_session=True)))

我需要加上 keep_session=True 这个设置。

然后,我不需要再写 session = DBSession(),只要直接用 DBSession 来进行查询和处理会话就可以了。

撰写回答