带有常规属性(不是关系)的SQLAlchemy DetachedInstanceError

2024-04-16 12:37:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我刚开始使用SQLAlchemy,得到一个DetachedInstanceError,在任何地方都找不到关于它的很多信息。我在会话外部使用实例,因此如果还没有加载任何关系,SQLAlchemy自然无法加载它们,但是,我正在访问的属性不是关系,实际上这个对象根本没有关系。我找到了一些解决方案,比如急切地加载,但是我不能应用于此,因为这不是一个关系。我甚至在关闭会话之前尝试过“触摸”这个属性,但它仍然无法阻止异常。哪种情况会导致非关系属性发生此异常,即使之前已成功访问过该属性一次?如能帮助调试此问题,我们将不胜感激。同时,我将尝试获得一个可复制的独立场景,并在此更新。

更新:这是带有几个堆栈的实际异常消息:

  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

Tags: clientidhomebin属性sqlalchemy关系egg
3条回答

我对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

经过大量的google和阅读sessions等内容,我意识到,由于我在提交之前捕获了实例r并将其返回,当同一条记录被发送回此函数进行另一次编辑/提交时,它已经丢失了会话。

为了解决这个问题,我只需查询数据库中刚刚更新的记录,并将其返回到session中,并将其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也避免了上面提到的错误,但我认为它实际上并没有解决错误,而且可能会导致许多其他问题

我在试图缩小导致异常的代码范围时找到了根本原因。在会话关闭后,我将相同的属性访问代码放在不同的位置,发现它绝对不会在查询会话关闭后立即导致任何问题。原来问题是在关闭一个新的会话(该会话已打开以更新对象)后开始出现的。一旦我了解到在会话关闭后对象的状态是不可用的,我就能够找到讨论这个问题的thread。有两种解决方案:

  • 保持会话打开(这是显而易见的)
  • 指定expire_on_commit=Falsesessionmaker()

第三个选项是在会话创建后,在会话上手动将expire_on_commit设置为False,类似于:session.expire_on_commit = False。我证实这解决了我的问题。

即使将expire_on_commit设置为False,我们也会遇到类似的错误。最后,它实际上是由于有两个sessionmaker被用来在不同的请求中创建会话而导致的。我真的不明白发生了什么,但是如果您看到这个异常的expire_on_commit=False,请确保没有初始化两个sessionmaker

相关问题 更多 >