mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-06 08:56:51 -04:00
modernize contains_eager() docs
Along with other loader options that are likely to require usage of populate_existing(), make sure contains_eager() documentation makes it absolutely clear that already-loaded collections are not overwritten. consolidate contains_eager() documentation into the narrative docs. Additionally, remove the "arbitrary statements" section, this is not a useful case and seems to be left over. Fixes: #5740 Change-Id: I10e320882990f511eefebcc88cfcc2277e553b50
This commit is contained in:
+36
-64
@@ -169,7 +169,7 @@ to the given filter criteria::
|
||||
|
||||
When using limiting criteria, if a particular collection is already loaded
|
||||
it won't be refreshed; to ensure the new criteria takes place, apply
|
||||
the :meth:`_orm.Query.populate_existing` option::
|
||||
the :meth:`_query.Query.populate_existing` option::
|
||||
|
||||
session.query(A).options(lazyload(A.bs.and_(B.id > 5))).populate_existing()
|
||||
|
||||
@@ -1062,10 +1062,9 @@ and additionally establish this as the basis for eager loading of ``User.address
|
||||
options(contains_eager(User.addresses))
|
||||
|
||||
|
||||
If the "eager" portion of the statement is "aliased", the ``alias`` keyword
|
||||
argument to :func:`~sqlalchemy.orm.contains_eager` may be used to indicate it.
|
||||
This is sent as a reference to an :func:`.aliased` or :class:`_expression.Alias`
|
||||
construct:
|
||||
If the "eager" portion of the statement is "aliased", the path
|
||||
should be specified using :meth:`.PropComparator.of_type`, which allows
|
||||
the specific :func:`_orm.aliased` construct to be passed:
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
@@ -1074,8 +1073,8 @@ construct:
|
||||
|
||||
# construct a Query object which expects the "addresses" results
|
||||
query = session.query(User).\
|
||||
outerjoin(adalias, User.addresses).\
|
||||
options(contains_eager(User.addresses, alias=adalias))
|
||||
outerjoin(User.addresses.of_type(adalias)).\
|
||||
options(contains_eager(User.addresses.of_type(adalias)))
|
||||
|
||||
# get results normally
|
||||
r = query.all()
|
||||
@@ -1092,13 +1091,7 @@ construct:
|
||||
|
||||
The path given as the argument to :func:`.contains_eager` needs
|
||||
to be a full path from the starting entity. For example if we were loading
|
||||
``Users->orders->Order->items->Item``, the string version would look like::
|
||||
|
||||
query(User).options(
|
||||
contains_eager('orders').
|
||||
contains_eager('items'))
|
||||
|
||||
Or using the class-bound descriptor::
|
||||
``Users->orders->Order->items->Item``, the option would be used as::
|
||||
|
||||
query(User).options(
|
||||
contains_eager(User.orders).
|
||||
@@ -1114,64 +1107,43 @@ by writing our SQL to load a subset of elements for collections or
|
||||
scalar attributes.
|
||||
|
||||
As an example, we can load a ``User`` object and eagerly load only particular
|
||||
addresses into its ``.addresses`` collection just by filtering::
|
||||
addresses into its ``.addresses`` collection by filtering the joined data,
|
||||
routing it using :func:`_orm.contains_eager`, also using
|
||||
:meth:`_query.Query.populate_existing` to ensure any already-loaded collections
|
||||
are overwritten::
|
||||
|
||||
q = session.query(User).join(User.addresses).\
|
||||
filter(Address.email.like('%ed%')).\
|
||||
options(contains_eager(User.addresses))
|
||||
q = session.query(User).\
|
||||
join(User.addresses).\
|
||||
filter(Address.email_address.like('%@aol.com')).\
|
||||
options(contains_eager(User.addresses)).\
|
||||
populate_existing()
|
||||
|
||||
The above query will load only ``User`` objects which contain at
|
||||
least ``Address`` object that contains the substring ``'ed'`` in its
|
||||
least ``Address`` object that contains the substring ``'aol.com'`` in its
|
||||
``email`` field; the ``User.addresses`` collection will contain **only**
|
||||
these ``Address`` entries, and *not* any other ``Address`` entries that are
|
||||
in fact associated with the collection.
|
||||
|
||||
.. warning::
|
||||
.. tip:: In all cases, the SQLAlchemy ORM does **not overwrite already loaded
|
||||
attributes and collections** unless told to do so. As there is an
|
||||
:term:`identity map` in use, it is often the case that an ORM query is
|
||||
returning objects that were in fact already present and loaded in memory.
|
||||
Therefore, when using :func:`_orm.contains_eager` to populate a collection
|
||||
in an alternate way, it is usually a good idea to use
|
||||
:meth:`_query.Query.populate_existing` as illustrated above so that an
|
||||
already-loaded collection is refreshed with the new data.
|
||||
:meth:`_query.Query.populate_existing` will reset **all** attributes that were
|
||||
already present, including pending changes, so make sure all data is flushed
|
||||
before using it. Using the :class:`_orm.Session` with its default behavior
|
||||
of :ref:`autoflush <session_flushing>` is sufficient.
|
||||
|
||||
Keep in mind that when we load only a subset of objects into a collection,
|
||||
that collection no longer represents what's actually in the database. If
|
||||
we attempted to add entries to this collection, we might find ourselves
|
||||
conflicting with entries that are already in the database but not locally
|
||||
loaded.
|
||||
|
||||
In addition, the **collection will fully reload normally** once the
|
||||
object or attribute is expired. This expiration occurs whenever the
|
||||
:meth:`.Session.commit`, :meth:`.Session.rollback` methods are used
|
||||
assuming default session settings, or the :meth:`.Session.expire_all`
|
||||
or :meth:`.Session.expire` methods are used.
|
||||
|
||||
For these reasons, prefer returning separate fields in a tuple rather
|
||||
than artificially altering a collection, when an object plus a custom
|
||||
set of related objects is desired::
|
||||
|
||||
q = session.query(User, Address).join(User.addresses).\
|
||||
filter(Address.email.like('%ed%'))
|
||||
|
||||
|
||||
Advanced Usage with Arbitrary Statements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``alias`` argument can be more creatively used, in that it can be made
|
||||
to represent any set of arbitrary names to match up into a statement.
|
||||
Below it is linked to a :func:`_expression.select` which links a set of column objects
|
||||
to a string SQL statement::
|
||||
|
||||
# label the columns of the addresses table
|
||||
eager_columns = select(
|
||||
addresses.c.address_id.label('a1'),
|
||||
addresses.c.email_address.label('a2'),
|
||||
addresses.c.user_id.label('a3')
|
||||
)
|
||||
|
||||
# select from a raw SQL statement which uses those label names for the
|
||||
# addresses table. contains_eager() matches them up.
|
||||
query = session.query(User).\
|
||||
from_statement("select users.*, addresses.address_id as a1, "
|
||||
"addresses.email_address as a2, "
|
||||
"addresses.user_id as a3 "
|
||||
"from users left outer join "
|
||||
"addresses on users.user_id=addresses.user_id").\
|
||||
options(contains_eager(User.addresses, alias=eager_columns))
|
||||
.. note:: The customized collection we load using :func:`_orm.contains_eager`
|
||||
is not "sticky"; that is, the next time this collection is loaded, it will
|
||||
be loaded with its usual default contents. The collection is subject
|
||||
to being reloaded if the object is expired, which occurs whenever the
|
||||
:meth:`.Session.commit`, :meth:`.Session.rollback` methods are used
|
||||
assuming default session settings, or the :meth:`.Session.expire_all`
|
||||
or :meth:`.Session.expire` methods are used.
|
||||
|
||||
Creating Custom Load Rules
|
||||
--------------------------
|
||||
|
||||
@@ -1006,40 +1006,18 @@ def contains_eager(loadopt, attr, alias=None):
|
||||
``User`` entity, and the returned ``Order`` objects would have the
|
||||
``Order.user`` attribute pre-populated.
|
||||
|
||||
When making use of aliases with :func:`.contains_eager`, the path
|
||||
should be specified using :meth:`.PropComparator.of_type`::
|
||||
It may also be used for customizing the entries in an eagerly loaded
|
||||
collection; queries will normally want to use the
|
||||
:meth:`_query.Query.populate_existing` method assuming the primary
|
||||
collection of parent objects may already have been loaded::
|
||||
|
||||
user_alias = aliased(User)
|
||||
sess.query(Order).\
|
||||
join((user_alias, Order.user)).\
|
||||
options(contains_eager(Order.user.of_type(user_alias)))
|
||||
sess.query(User).\
|
||||
join(User.addresses).\
|
||||
filter(Address.email_address.like('%@aol.com')).\
|
||||
options(contains_eager(User.addresses)).\
|
||||
populate_existing()
|
||||
|
||||
:meth:`.PropComparator.of_type` is also used to indicate a join
|
||||
against specific subclasses of an inherting mapper, or
|
||||
of a :func:`.with_polymorphic` construct::
|
||||
|
||||
# employees of a particular subtype
|
||||
sess.query(Company).\
|
||||
outerjoin(Company.employees.of_type(Manager)).\
|
||||
options(
|
||||
contains_eager(
|
||||
Company.employees.of_type(Manager),
|
||||
)
|
||||
)
|
||||
|
||||
# employees of a multiple subtypes
|
||||
wp = with_polymorphic(Employee, [Manager, Engineer])
|
||||
sess.query(Company).\
|
||||
outerjoin(Company.employees.of_type(wp)).\
|
||||
options(
|
||||
contains_eager(
|
||||
Company.employees.of_type(wp),
|
||||
)
|
||||
)
|
||||
|
||||
The :paramref:`.contains_eager.alias` parameter is used for a similar
|
||||
purpose, however the :meth:`.PropComparator.of_type` approach should work
|
||||
in all cases and is more effective and explicit.
|
||||
See the section :ref:`contains_eager` for complete usage details.
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -1690,7 +1668,7 @@ def with_expression(loadopt, key, expression):
|
||||
|
||||
.. note:: the target attribute is populated only if the target object
|
||||
is **not currently loaded** in the current :class:`_orm.Session`
|
||||
unless the :meth:`_orm.Query.populate_existing` method is used.
|
||||
unless the :meth:`_query.Query.populate_existing` method is used.
|
||||
Please refer to :ref:`mapper_querytime_expression` for complete
|
||||
usage details.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user