:class: faq
:backlinks: none
+.. _faq_session_identity:
I'm re-loading data with my Session but it isn't seeing changes that I committed elsewhere
------------------------------------------------------------------------------------------
:ref:`migration_20_toplevel`
+
+
Adding New or Existing Items
----------------------------
been rolled back already - this is so that the overall nesting pattern of
so-called "subtransactions" is consistently maintained.
+
+Expiring / Refreshing
+---------------------
+
+An important consideration that will often come up when using the
+:class:`_orm.Session` is that of dealing with the state that is present on
+objects that have been loaded from the database, in terms of keeping them
+synchronized with the current state of the transaction. The SQLAlchemy
+ORM is based around the concept of an :term:`identity map` such that when
+an object is "loaded" from a SQL query, there will be a unique Python
+object instance maintained corresponding to a particular database identity.
+This means if we emit two separate queries, each for the same row, and get
+a mapped object back, the two queries will have returned the same Python
+object::
+
+ >>> u1 = session.query(User).filter(id=5).first()
+ >>> u2 = session.query(User).filter(id=5).first()
+ >>> u1 is u2
+ True
+
+Following from this, when the ORM gets rows back from a query, it will
+**skip the population of attributes** for an object that's already loaded.
+The design assumption here is to assume a transaction that's perfectly
+isolated, and then to the degree that the transaction isn't isolated, the
+application can take steps on an as-needed basis to refresh objects
+from the database transaction. The FAQ entry at :ref:`faq_session_identity`
+discusses this concept in more detail.
+
+When an ORM mapped object is loaded into memory, there are three general
+ways to refresh its contents with new data from the current transaction:
+
+* **the expire() method** - the :meth:`_orm.Session.expire` method will
+ erase the contents of selected or all attributes of an object, such that they
+ will be loaded from the database when they are next accessed, e.g. using
+ a :term:`lazy loading` pattern::
+
+ session.expire(u1)
+ u1.some_attribute # <-- lazy loads from the transaction
+ ..
+
+* **the refresh() method** - closely related is the :meth:`_orm.Session.refresh`
+ method, which does everything the :meth:`_orm.Session.expire` method does
+ but also emits one or more SQL queries immediately to actually refresh
+ the contents of the object::
+
+ session.refresh(u1) # <-- emits a SQL query
+ u1.some_attribute # <-- is refreshed from the transaction
+
+ ..
+
+* **the populate_existing() method** - this method is actually on the
+ :class:`_orm.Query` object as :meth:`_orm.Query.populate_existing`
+ and indicates that it should return objects that are unconditionally
+ re-populated from their contents in the database::
+
+ u2 = session.query(User).populate_existing().filter(id=5).first()
+
+ ..
+
+Further discussion on the refresh / expire concept can be found at
+:ref:`session_expire`.
+
+.. seealso::
+
+ :ref:`session_expire`
+
+ :ref:`faq_session_identity`
+
+
+
.. _bulk_update_delete:
Bulk UPDATE and DELETE
# expire only attributes obj1.attr1, obj1.attr2
session.expire(obj1, ['attr1', 'attr2'])
+The :meth:`.Session.expire_all` method allows us to essentially call
+:meth:`.Session.expire` on all objects contained within the :class:`.Session`
+at once::
+
+ session.expire_all()
+
The :meth:`~.Session.refresh` method has a similar interface, but instead
of expiring, it emits an immediate SELECT for the object's row immediately::
# reload obj1.attr1, obj1.attr2
session.refresh(obj1, ['attr1', 'attr2'])
-The :meth:`.Session.expire_all` method allows us to essentially call
-:meth:`.Session.expire` on all objects contained within the :class:`.Session`
-at once::
+An alternative method of refreshing which is often more flexible is to
+use the :meth:`_orm.Query.populate_existing` method of :class:`_orm.Query`.
+With this option, all of the ORM objects returned by the :class:`_orm.Query`
+will be refreshed with the contents of what was loaded in the SELECT::
+
+ for user in session.query(User).populate_existing().filter(User.name.in_(['a', 'b', 'c'])):
+ print(user) # will be refreshed for those columns that came back from the query
- session.expire_all()
What Actually Loads
~~~~~~~~~~~~~~~~~~~
:meth:`.Session.refresh`
+ :meth:`_orm.Query.populate_existing` - :class:`_orm.Query` method that refreshes
+ all matching objects in the identity map against the results of a
+ SELECT statement.
+
:term:`isolation` - glossary explanation of isolation which includes links
to Wikipedia.
``populate_existing=True`` option to the
:meth:`_orm.Query.execution_options` method.
+ .. seealso::
+
+ :ref:`session_expire` - in the ORM :class:`_orm.Session`
+ documentation
"""
self.load_options += {"_populate_existing": True}
E.g.::
- q = sess.query(User).with_for_update(nowait=True, of=User)
+ q = sess.query(User).populate_existing().with_for_update(nowait=True, of=User)
The above query on a PostgreSQL backend will render like::
SELECT users.id AS users_id FROM users FOR UPDATE OF users NOWAIT
+ .. note:: It is generally a good idea to combine the use of the
+ :meth:`_orm.Query.populate_existing` method when using the
+ :meth:`_orm.Query.with_for_update` method. The purpose of
+ :meth:`_orm.Query.populate_existing` is to force all the data read
+ from the SELECT to be populated into the ORM objects returned,
+ even if these objects are already in the :term:`identity map`.
+
.. seealso::
:meth:`_expression.GenerativeSelect.with_for_update`
- Core level method with
full argument and behavioral description.
- """
+ :meth:`_orm.Query.populate_existing` - overwrites attributes of
+ objects already loaded in the identity map.
+
+ """ # noqa: E501
+
self._for_update_arg = ForUpdateArg(
read=read,
nowait=nowait,
:meth:`.Session.expire_all`
+ :meth:`_orm.Query.populate_existing`
+
"""
try:
state = attributes.instance_state(instance)
:meth:`.Session.refresh`
+ :meth:`_orm.Query.populate_existing`
+
"""
for state in self.identity_map.all_states():
state._expire(state.dict, self.identity_map._modified)
:meth:`.Session.refresh`
+ :meth:`_orm.Query.populate_existing`
+
"""
try:
state = attributes.instance_state(instance)