Proper way to prevent SQLAlchemy from re-running queries on expired objects?

夙愿已清 提交于 2020-01-03 20:48:33

问题


I'm having trouble wrapping my head around how to deal with expired sqlalchemy objects in a flask request. Let's say I do something like the following:

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)

and then in page.html:

{% for foo in foos %}
  {{ foo. name }}
{% endfor %}

SQLAlchemy will then do a select query for each foo in the template loop, because the session.commit() marked the foos collection as expired. If I know that there is no way for foos to actually have changed, what is the right way to prevent len(foos) queries from being executed? Similarly, if foos has changed, what is the right way to refresh the data with a single query rather than many?


回答1:


If you know there is no way for foos to have been updated, why ever issue the db.session.commit() at all? If it's sometimes, then put some logic in that triggers the commit only if something has been updated.

You could just add a foos = Foo.query.all() underneath the db.session.commit() line. That would then just fire a single query for all the data, not one per row.

As you say, committing the data would set it as expired, so they'll need to be re-queried. Perhaps you could refresh the session rather then re-querying, more information on that in the SQLAlchemy documentation which seems to indicate you could do session.refresh(object).

Update: Using Two Sessions

You could use a second session, you'll use it to query the Foo, then the other session to handle the Bars. That will leave foos untouched when you commit, so you won't have to hit it up again.

Here's a rough example:

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)

Also, I wonder if you could handle it with a single session, that's been configured with expire_on_commit=False from the documentation:

"Another behavior of commit() is that by default it expires the state of all instances present after the commit is complete. This is so that when the instances are next accessed, either through attribute access or by them being present in a Query result set, they receive the most recent state. To disable this behavior, configure sessionmaker with expire_on_commit=False"

Using Session.expunge

Remove the object from the session as required

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



回答2:


I have a slightly different approach, which I don't recommend for 99% of situations. ( but I'll share anyways )

I do agressive caching of data fetched through SqlAlchemy. In order to feign a level parity between live SqlAlchemy Objects and cached data, I do the following ( referencing https://gist.github.com/jvanasco/01af92e100769d52f7b8 )

  • when data goes into the Cache , i turn it into a raw dict ( ie, remove all the sqlalchemy info. i just want the table's information )

  • when i pull data from the Cache, i turn it into an "ObjectifiedDict". This is a dict that simply offers a dot-based attribute access -- just like SqlAlchemy objects.

  • the routines to pull data from the cache can also specify attributes as lazyloaded functions ( this is written as i pull things out of the cache). this way i can relate the photo attribute of a 'Useraccount' object as a function to pull a specific photo out of the cache.

With this approach, my read-only sections use the same templates as the writable sections of the application -- the only difference being that if you looked at the Views, the objects on one section are a version of dicts while the others are actual SqlAlchemy.

I don't recommend this for 99% of situations. But on those 1% of situations when you're trying to persist cached data around , I've found this to be the best solution.



来源:https://stackoverflow.com/questions/14898379/proper-way-to-prevent-sqlalchemy-from-re-running-queries-on-expired-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!