防止 SQLAlchemy 对过期对象重新运行查询的正确方法?

adh*_*ris 3 python sqlalchemy flask flask-sqlalchemy

我在思考如何处理 Flask 请求中过期的 sqlalchemy 对象时遇到了麻烦。假设我执行以下操作:

from models import Foo, Bar

@app.route("/page")
def page():
  foos = Foo.query.all()

  for foo in foos:
    b = Bar(foo.data)
    db.session.add(b)

  db.session.commit()

  return render_template('page.html', foos=foos)
Run Code Online (Sandbox Code Playgroud)

然后在page.html

{% for foo in foos %}
  {{ foo. name }}
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

然后 SQLAlchemy 将对模板循环中的每个 foo 执行选择查询,因为session.commit()foos集合标记为已过期。如果我知道无法foos实际更改,那么防止len(foos)执行查询的正确方法是什么?同样,如果foos 更改,使用单个查询而不是多个查询刷新数据的正确方法是什么?

Doo*_*beh 5

如果您知道 foos 无法更新,为什么还要发布db.session.commit()?如果是有时,则在其中放入一些逻辑,仅在某些内容已更新时才触发提交。

您可以foos = Foo.query.all()在该db.session.commit()行下方添加一个。然后只会对所有数据触发一个查询,而不是每行一个。

正如您所说,提交数据会将其设置为已过期,因此需要重新查询它们。也许您可以刷新会话而不是重新查询,SQLAlchemy 文档中的更多信息似乎表明您可以这样做session.refresh(object).

更新:使用两个会话

您可以使用第二个会话,您将使用它来查询Foo,然后另一个会话来处理Bars. 当您提交时,这将使 foos 保持不变,因此您不必再次点击它。

这是一个粗略的例子:

from flask.ext.sqlalchemy import Session

@app.route('/example/')
def home():
    session_two = Session(bind=db.engine.connect())
    foos = session_two.query(Foo).all()

    for foo in foos:
        db.session.add(Bar(foo))
    db.session.commit()

    return render_template_string('''
        {% for foo in foos %}
            {{ foo.name }}
        {% endfor %}
    ''', foos=foos)
Run Code Online (Sandbox Code Playgroud)

另外,我想知道您是否可以通过单个会话来处理它,这是expire_on_commit=False 从文档中配置

“commit() 的另一个行为是,默认情况下,它会在提交完成后使所有实例的状态过期。这样在下次访问实例时,通过属性访问或通过它们出现在查询结果集中,他们会收到最新的状态。要禁用此行为,请使用 expire_on_commit=False 配置 sessionmaker”

使用 Session.expunge

根据需要从会话中删除对象

@app.route('/')
def home():
    foos = Foo.query.all()
    for foo in foos:
        db.session.add(Bar(foo))
        db.session.expunge(foo)
    db.session.commit()

    return render_template_string('''
        {% for foo in foos %}
            {{ foo.name }}
        {% endfor %}
    ''', foos=foos)
Run Code Online (Sandbox Code Playgroud)