代码之家  ›  专栏  ›  技术社区  ›  haridsv

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

  •  39
  • haridsv  · 技术社区  · 15 年前

    我刚开始使用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)
    

    而field 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
    
    5 回复  |  直到 15 年前
        1
  •  67
  •   haridsv    15 年前

    thread 讨论了同样的问题。两种解决方案是:

    • 指定 expire_on_commit=False sessionmaker()

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

        2
  •  11
  •   glyphobet    12 年前

    我们也遇到了类似的错误,即使 expire_on_commit False . 最终它实际上是由有两个 sessionmaker 它们都习惯于在不同的请求中进行会话。我真的不明白发生了什么,但是如果你看到这个例外 expire_on_commit=False ,确保你没有两个 会话生成器 已初始化。

        3
  •  3
  •   Frank    7 年前

    我也有类似的问题 DetachedInstanceError: Instance <> is not bound to a Session;

    情况非常简单,我将会话和要更新的记录传递给我的函数,它将合并记录并将其提交到数据库。在第一个示例中,我会得到错误,因为我很懒,认为我可以返回合并的对象,以便更新我的操作记录(即 is_modified 是否已修改

            def EditStaff(self, session, record):
                try:
                        r = session.merge(record)
                        session.commit()
                        return r
                except:
                        return False
    

    在google和阅读sessions等内容之后,我意识到,由于我在提交之前捕获了实例r并返回了它,当同一条记录被发送回这个函数进行另一次编辑/提交时,它就失去了会话。

    所以要解决这个问题,我只需在数据库中查询刚刚更新的记录,并返回它以保持会话状态并标记其 值返回为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 也避免了上面提到的错误,但我不认为它实际上解决了错误,并可能导致许多其他问题。

        4
  •  0
  •   seaders    6 年前

    抛开我的事业;把溶液放入戒指里,我用 flask flask-sqlalchemy 来管理我所有的会话内容。当我通过站点进行操作时,这是很好的,但是当通过命令行和脚本进行操作时,您必须确保执行flask-y操作的任何操作都必须使用flask上下文。

    所以,在我的情况下,我需要从数据库(使用 ),然后将它们呈现到模板(使用flask的render\u模板),然后通过电子邮件(使用 flask-mail ).

    在代码中,我所做的是,

    def render_obj(db_obj):
      with app.app_context():
        return render_template('template_for_my_db_obj.html', db_obj=db_obj
    
    def get_renders():
      my_db_objs = MyDbObj.query.all()
    
      renders = []
      for day, _db_objs in itertools.groupby(my_db_objs, MyDbObj.get_date):
        renders.extend(list(map(render_obj, _db_obj)))
    
      return renders
    
    def email_report():
      renders = get_renders()
      report = '\n'.join(renders)
    
      with app.app_context():
        mail.send(Message('Subject', ['me@me.com'], html=report))
    

    当我跑步的时候,我会通过 第一 _db_obj ,但之后的任何一次运行都会出现错误。

    with app.app_context() .

    基本上,当你 别这样 ,包括刷新数据库连接。其中一个原因就是摆脱了上一个环节,也就是所有人都在进行的环节 my_db_objs

    def render_obj(db_obj):
      return render_template('template_for_my_db_obj.html', db_obj=db_obj
    
    def get_renders():
      my_db_objs = MyDbObj.query.all()
    
      renders = []
      for day, _db_objs in itertools.groupby(my_db_objs, MyDbObj.get_date):
        renders.extend(list(map(render_obj, _db_obj)))
    
      return renders
    
    def email_report():
      with app.app_context():
        renders = get_renders()
        report = '\n'.join(renders)
    
        mail.send(Message('Subject', ['me@me.com'], html=report))
    

    只有1个 使用app.app\u context() 把他们都包起来了。您需要做的主要事情(如果您有像我这样的设置)是确保您使用的任何dB对象“在”您使用的任何app\u上下文中。如果您执行我在第一次迭代中所做的操作,那么您的所有dB对象都将丢失会话,并以 DetachedInstanceError 像我一样。

        5
  •  -1
  •   Rick Jim DeLaHunt    8 年前

    至于我(新手),我在缩进上犯了一个错误,并在循环中关闭了会话,在循环中我循环每一行,执行一些操作,每次都提交。

    所以对于像我这样的新手,在设置 expire_on_commit=False ,它可能会导致你的另一个陷阱。