|
2 | 2 | Additional Persistence Techniques |
3 | 3 | ================================= |
4 | 4 |
|
| 5 | +.. _session_deleting_from_collections: |
| 6 | + |
| 7 | +Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships |
| 8 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 9 | + |
| 10 | +The ORM in general never modifies the contents of a collection or scalar |
| 11 | +relationship during the flush process. This means, if your class has a |
| 12 | +:func:`_orm.relationship` that refers to a collection of objects, or a reference |
| 13 | +to a single object such as many-to-one, the contents of this attribute will |
| 14 | +not be modified when the flush process occurs. Instead, it is expected |
| 15 | +that the :class:`.Session` would eventually be expired, either through the expire-on-commit behavior of |
| 16 | +:meth:`.Session.commit` or through explicit use of :meth:`.Session.expire`. |
| 17 | +At that point, any referenced object or collection associated with that |
| 18 | +:class:`.Session` will be cleared and will re-load itself upon next access. |
| 19 | + |
| 20 | +A common confusion that arises regarding this behavior involves the use of the |
| 21 | +:meth:`~.Session.delete` method. When :meth:`.Session.delete` is invoked upon |
| 22 | +an object and the :class:`.Session` is flushed, the row is deleted from the |
| 23 | +database. Rows that refer to the target row via foreign key, assuming they |
| 24 | +are tracked using a :func:`_orm.relationship` between the two mapped object types, |
| 25 | +will also see their foreign key attributes UPDATED to null, or if delete |
| 26 | +cascade is set up, the related rows will be deleted as well. However, even |
| 27 | +though rows related to the deleted object might be themselves modified as well, |
| 28 | +**no changes occur to relationship-bound collections or object references on |
| 29 | +the objects** involved in the operation within the scope of the flush |
| 30 | +itself. This means if the object was a |
| 31 | +member of a related collection, it will still be present on the Python side |
| 32 | +until that collection is expired. Similarly, if the object were |
| 33 | +referenced via many-to-one or one-to-one from another object, that reference |
| 34 | +will remain present on that object until the object is expired as well. |
| 35 | + |
| 36 | +Below, we illustrate that after an ``Address`` object is marked |
| 37 | +for deletion, it's still present in the collection associated with the |
| 38 | +parent ``User``, even after a flush:: |
| 39 | + |
| 40 | + >>> address = user.addresses[1] |
| 41 | + >>> session.delete(address) |
| 42 | + >>> session.flush() |
| 43 | + >>> address in user.addresses |
| 44 | + True |
| 45 | + |
| 46 | +When the above session is committed, all attributes are expired. The next |
| 47 | +access of ``user.addresses`` will re-load the collection, revealing the |
| 48 | +desired state:: |
| 49 | + |
| 50 | + >>> session.commit() |
| 51 | + >>> address in user.addresses |
| 52 | + False |
| 53 | + |
| 54 | +There is a recipe for intercepting :meth:`.Session.delete` and invoking this |
| 55 | +expiration automatically; see `ExpireRelationshipOnFKChange <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ExpireRelationshipOnFKChange>`_ for this. However, the usual practice of |
| 56 | +deleting items within collections is to forego the usage of |
| 57 | +:meth:`~.Session.delete` directly, and instead use cascade behavior to |
| 58 | +automatically invoke the deletion as a result of removing the object from the |
| 59 | +parent collection. The ``delete-orphan`` cascade accomplishes this, as |
| 60 | +illustrated in the example below:: |
| 61 | + |
| 62 | + class User(Base): |
| 63 | + __tablename__ = 'user' |
| 64 | + |
| 65 | + # ... |
| 66 | + |
| 67 | + addresses = relationship( |
| 68 | + "Address", cascade="all, delete-orphan") |
| 69 | + |
| 70 | + # ... |
| 71 | + |
| 72 | + del user.addresses[1] |
| 73 | + session.flush() |
| 74 | + |
| 75 | +Where above, upon removing the ``Address`` object from the ``User.addresses`` |
| 76 | +collection, the ``delete-orphan`` cascade has the effect of marking the ``Address`` |
| 77 | +object for deletion in the same way as passing it to :meth:`~.Session.delete`. |
| 78 | + |
| 79 | +The ``delete-orphan`` cascade can also be applied to a many-to-one |
| 80 | +or one-to-one relationship, so that when an object is de-associated from its |
| 81 | +parent, it is also automatically marked for deletion. Using ``delete-orphan`` |
| 82 | +cascade on a many-to-one or one-to-one requires an additional flag |
| 83 | +:paramref:`_orm.relationship.single_parent` which invokes an assertion |
| 84 | +that this related object is not to shared with any other parent simultaneously:: |
| 85 | + |
| 86 | + class User(Base): |
| 87 | + # ... |
| 88 | + |
| 89 | + preference = relationship( |
| 90 | + "Preference", cascade="all, delete-orphan", |
| 91 | + single_parent=True) |
| 92 | + |
| 93 | + |
| 94 | +Above, if a hypothetical ``Preference`` object is removed from a ``User``, |
| 95 | +it will be deleted on flush:: |
| 96 | + |
| 97 | + some_user.preference = None |
| 98 | + session.flush() # will delete the Preference object |
| 99 | + |
| 100 | +.. seealso:: |
| 101 | + |
| 102 | + :ref:`unitofwork_cascades` for detail on cascades. |
| 103 | + |
| 104 | + |
| 105 | + |
5 | 106 | .. _flush_embedded_sql_expressions: |
6 | 107 |
|
7 | 108 | Embedding SQL Insert/Update Expressions into a Flush |
|
0 commit comments