State Management

Kurze Einführung in Objektzustände

Es ist hilfreich, die Zustände zu kennen, die eine Instanz innerhalb einer Sitzung haben kann

  • Transient – eine Instanz, die sich nicht in einer Sitzung befindet und nicht in der Datenbank gespeichert ist; d. h. sie hat keine Datenbankidentität. Die einzige Beziehung, die ein solches Objekt zur ORM hat, ist, dass seine Klasse eine Mapper zugeordnet hat.

  • Pending – Wenn Sie eine transiente Instanz Session.add(), wird sie pending. Sie wurde noch nicht tatsächlich in die Datenbank gespült (flushed), aber sie wird es beim nächsten Spülvorgang sein.

  • Persistent – Eine Instanz, die sich in der Sitzung befindet und einen Eintrag in der Datenbank hat. Sie erhalten persistente Instanzen, indem Sie entweder spülen, damit die pending-Instanzen persistent werden, oder indem Sie die Datenbank nach vorhandenen Instanzen abfragen (oder persistente Instanzen aus anderen Sitzungen in Ihre lokale Sitzung verschieben).

  • Deleted – Eine Instanz, die während eines Spülvorgangs gelöscht wurde, die Transaktion aber noch nicht abgeschlossen ist. Objekte in diesem Zustand sind im Wesentlichen das Gegenteil von "pending"; wenn die Transaktion der Sitzung committet wird, wechselt das Objekt in den detached-Zustand. Alternativ, wenn die Transaktion der Sitzung zurückgerollt wird, kehrt ein gelöschtes Objekt in den persistenten Zustand zurück.

  • Detached – eine Instanz, die einem Eintrag in der Datenbank entspricht oder entsprach, sich aber derzeit in keiner Sitzung befindet. Das detached-Objekt enthält zwar einen Datenbankidentitätsmarker, da es aber keiner Sitzung zugeordnet ist, ist unbekannt, ob diese Datenbankidentität in der Zieldatenbank tatsächlich existiert. Detached-Objekte können sicher normal verwendet werden, mit der Ausnahme, dass sie keine Möglichkeit haben, nicht geladene Attribute oder Attribute zu laden, die zuvor als "expired" markiert wurden.

Für eine tiefere Betrachtung aller möglichen Zustandsübergänge siehe den Abschnitt Objektlebenszyklusereignisse, der jeden Übergang sowie die programmatische Verfolgung jedes einzelnen beschreibt.

Den aktuellen Zustand eines Objekts abrufen

Der tatsächliche Zustand jedes gemappten Objekts kann jederzeit mit der Funktion inspect() auf einer gemappten Instanz eingesehen werden; diese Funktion gibt das entsprechende InstanceState-Objekt zurück, das den internen ORM-Zustand für das Objekt verwaltet. InstanceState bietet unter anderem boolesche Attribute, die den Persistenzzustand des Objekts angeben, einschließlich

Z. B.

>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.persistent
True

Siehe auch

Inspektion von gemappten Instanzen – weitere Beispiele für InstanceState

Sitzungsattribute

Die Session selbst verhält sich etwas wie eine Menge. Alle vorhandenen Elemente können über die Iterator-Schnittstelle abgerufen werden

for obj in session:
    print(obj)

Und die Anwesenheit kann mit regulären "contains"-Semantiken getestet werden

if obj in session:
    print("Object is present")

Die Sitzung verfolgt auch alle neu erstellten (d. h. pending), alle Objekte, die seit ihrer letzten Ladung oder Speicherung Änderungen erfahren haben (d. h. "dirty"), und alles, was als gelöscht markiert wurde

# pending objects recently added to the Session
session.new

# persistent objects which currently have changes detected
# (this collection is now created on the fly each time the property is called)
session.dirty

# persistent objects that have been marked as deleted via session.delete(obj)
session.deleted

# dictionary of all persistent objects, keyed on their
# identity key
session.identity_map

(Dokumentation: Session.new, Session.dirty, Session.deleted, Session.identity_map).

Sitzungsreferenzierungsverhalten

Objekte innerhalb der Sitzung werden *schwach referenziert*. Das bedeutet, dass sie, wenn sie in der externen Anwendung dereferenziert werden, auch aus dem Gültigkeitsbereich der Session herausfallen und der Garbage Collection durch den Python-Interpreter unterliegen. Ausnahmen hierfür sind Objekte, die pending sind, Objekte, die als gelöscht markiert sind, oder persistente Objekte, die ausstehende Änderungen enthalten. Nach einem vollständigen Spülvorgang sind diese Sammlungen alle leer, und alle Objekte werden wieder schwach referenziert.

Um Objekte in der Session stark referenziert zu halten, ist normalerweise ein einfacher Ansatz ausreichend. Beispiele für extern verwaltetes starkes Referenzierungsverhalten sind das Laden von Objekten in ein lokales Dictionary, das nach Primärschlüssel sortiert ist, oder in Listen oder Mengen für die Dauer, für die sie referenziert bleiben müssen. Diese Sammlungen können nach Wunsch mit einer Session verknüpft werden, indem sie im Session.info-Dictionary abgelegt werden.

Ein ereignisbasierter Ansatz ist ebenfalls machbar. Ein einfaches Rezept, das ein "starkes Referenzierungs"-Verhalten für alle Objekte bietet, solange sie sich im persistenten Zustand befinden, ist wie folgt:

from sqlalchemy import event


def strong_reference_session(session):
    @event.listens_for(session, "pending_to_persistent")
    @event.listens_for(session, "deleted_to_persistent")
    @event.listens_for(session, "detached_to_persistent")
    @event.listens_for(session, "loaded_as_persistent")
    def strong_ref_object(sess, instance):
        if "refs" not in sess.info:
            sess.info["refs"] = refs = set()
        else:
            refs = sess.info["refs"]

        refs.add(instance)

    @event.listens_for(session, "persistent_to_detached")
    @event.listens_for(session, "persistent_to_deleted")
    @event.listens_for(session, "persistent_to_transient")
    def deref_object(sess, instance):
        sess.info["refs"].discard(instance)

Oben fangen wir die Ereignisse SessionEvents.pending_to_persistent(), SessionEvents.detached_to_persistent(), SessionEvents.deleted_to_persistent() und SessionEvents.loaded_as_persistent() ab, um Objekte abzufangen, wenn sie in den persistenten Übergang eintreten, und die Hooks SessionEvents.persistent_to_detached() und SessionEvents.persistent_to_deleted(), um Objekte abzufangen, wenn sie den persistenten Zustand verlassen.

Die obige Funktion kann für jede Session aufgerufen werden, um ein starkes Referenzierungsverhalten pro Session bereitzustellen

from sqlalchemy.orm import Session

my_session = Session()
strong_reference_session(my_session)

Sie kann auch für jedes sessionmaker aufgerufen werden

from sqlalchemy.orm import sessionmaker

maker = sessionmaker()
strong_reference_session(maker)

Zusammenführen

Session.merge() überträgt den Zustand von einem externen Objekt auf eine neue oder bereits vorhandene Instanz innerhalb einer Sitzung. Es gleicht auch die eingehenden Daten mit dem Zustand der Datenbank ab und erzeugt einen Historienstrom, der für den nächsten Spülvorgang angewendet wird, oder kann alternativ eine einfache "Übertragung" des Zustands erzeugen, ohne den Änderungsverlauf zu erzeugen oder auf die Datenbank zuzugreifen. Die Verwendung erfolgt wie folgt:

merged_object = session.merge(existing_object)

Wenn eine Instanz übergeben wird, werden folgende Schritte ausgeführt:

  • Es wird der Primärschlüssel der Instanz untersucht. Wenn er vorhanden ist, wird versucht, diese Instanz in der lokalen Identitätszuordnung (identity map) zu lokalisieren. Wenn das Flag load=True auf seinem Standardwert belassen wird, wird auch die Datenbank nach diesem Primärschlüssel durchsucht, falls er lokal nicht gefunden wurde.

  • Wenn die übergebene Instanz keinen Primärschlüssel hat oder wenn keine Instanz mit dem angegebenen Primärschlüssel gefunden werden kann, wird eine neue Instanz erstellt.

  • Der Zustand der übergebenen Instanz wird dann auf die lokalisierte/neu erstellte Instanz kopiert. Für Attributwerte, die auf der Quellinstanz vorhanden sind, wird der Wert auf die Zielinstanz übertragen. Für Attributwerte, die nicht auf der Quellinstanz vorhanden sind, wird das entsprechende Attribut auf der Zielinstanz aus dem Speicher verfallen gelassen, was jeden lokal vorhandenen Wert des Zielobjekts für dieses Attribut verwirft, aber keine direkte Änderung am datenbankbeständigen Wert für dieses Attribut vorgenommen wird.

    Wenn das Flag load=True auf seinem Standardwert belassen wird, löst dieser Kopiervorgang Ereignisse aus und lädt die nicht geladenen Sammlungen des Zielobjekts für jedes auf dem Quellobjekt vorhandene Attribut, damit der eingehende Zustand mit dem in der Datenbank vorhandenen abgeglichen werden kann. Wenn load als False übergeben wird, werden die eingehenden Daten direkt "gestempelt", ohne einen Verlauf zu erzeugen.

  • Der Vorgang wird auf verwandte Objekte und Sammlungen übertragen (cascaded), wie durch die merge-Kaskade angezeigt (siehe Kaskaden).

  • Die neue Instanz wird zurückgegeben.

Bei Session.merge() wird die übergebene "Quell"-Instanz nicht modifiziert und auch nicht mit der Ziel-Session assoziiert und bleibt verfügbar, um mit beliebigen anderen Session-Objekten zusammengeführt zu werden. Session.merge() ist nützlich, um den Zustand jeder Art von Objektstruktur zu übernehmen, unabhängig von ihrem Ursprung oder ihren aktuellen Sitzungszuordnungen, und ihren Zustand in eine neue Sitzung zu kopieren. Hier sind einige Beispiele:

  • Eine Anwendung, die eine Objektstruktur aus einer Datei liest und sie in der Datenbank speichern möchte, kann die Datei parsen, die Struktur aufbauen und dann Session.merge() verwenden, um sie in der Datenbank zu speichern und sicherzustellen, dass die Daten aus der Datei zur Formulierung des Primärschlüssels jedes Elements der Struktur verwendet werden. Später, wenn sich die Datei geändert hat, kann derselbe Prozess erneut ausgeführt werden, der eine leicht veränderte Objektstruktur erzeugt, die dann erneut merged werden kann, und die Session wird die Datenbank automatisch aktualisieren, um diese Änderungen widerzuspiegeln, indem sie jedes Objekt anhand des Primärschlüssels aus der Datenbank lädt und dann seinen Zustand mit dem neuen, bereitgestellten Zustand aktualisiert.

  • Eine Anwendung speichert Objekte in einem In-Memory-Cache, der von vielen Session-Objekten gleichzeitig gemeinsam genutzt wird. Session.merge() wird jedes Mal verwendet, wenn ein Objekt aus dem Cache abgerufen wird, um eine lokale Kopie davon in jeder Session zu erstellen, die es anfordert. Das gecachte Objekt bleibt detached; nur sein Zustand wird in Kopien von sich selbst verschoben, die lokal zu einzelnen Session-Objekten sind.

    Im Caching-Anwendungsfall wird häufig das Flag load=False verwendet, um den Aufwand des Abgleichs des Objektzustands mit der Datenbank zu vermeiden. Es gibt auch eine "Bulk"-Version von Session.merge() namens Query.merge_result(), die für mit Cache erweiterte Query-Objekte entwickelt wurde – siehe den Abschnitt Dogpile Caching.

  • Eine Anwendung möchte den Zustand einer Reihe von Objekten in eine Session übertragen, die von einem Worker-Thread oder einem anderen gleichzeitigen System verwaltet wird. Session.merge() erstellt eine Kopie jedes Objekts, das in diese neue Session platziert werden soll. Am Ende des Vorgangs behält der Eltern-Thread/Prozess die ursprünglichen Objekte, und der Thread/Worker kann mit lokalen Kopien dieser Objekte fortfahren.

    Im Anwendungsfall "Übertragung zwischen Threads/Prozessen" möchte die Anwendung möglicherweise das Flag load=False verwenden, um den Aufwand und redundante SQL-Abfragen während der Datenübertragung zu vermeiden.

Tipps zum Zusammenführen

Session.merge() ist eine äußerst nützliche Methode für viele Zwecke. Sie befasst sich jedoch mit der komplexen Grenze zwischen Objekten, die transient/detached sind, und solchen, die persistent sind, sowie mit der automatischen Übertragung von Zuständen. Die große Vielfalt der hier auftretenden Szenarien erfordert oft einen sorgfältigeren Umgang mit dem Zustand von Objekten. Häufige Probleme beim Zusammenführen beinhalten einige unerwartete Zustände bezüglich des Objekts, das an Session.merge() übergeben wird.

Verwenden wir das kanonische Beispiel der Objekte User und Address

class User(Base):
    __tablename__ = "user"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")


class Address(Base):
    __tablename__ = "address"

    id = mapped_column(Integer, primary_key=True)
    email_address = mapped_column(String(50), nullable=False)
    user_id = mapped_column(Integer, ForeignKey("user.id"), nullable=False)

Angenommen, ein User-Objekt mit einer Address, das bereits persistent ist

>>> u1 = User(name="ed", addresses=[Address(email_address="ed@ed.com")])
>>> session.add(u1)
>>> session.commit()

Wir erstellen nun a1, ein Objekt außerhalb der Sitzung, das wir über die bestehende Address zusammenführen möchten

>>> existing_a1 = u1.addresses[0]
>>> a1 = Address(id=existing_a1.id)

Eine Überraschung würde auftreten, wenn wir dies sagen würden

>>> a1.user = u1
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.orm.exc.FlushError: New instance <Address at 0x1298f50>
with identity key (<class '__main__.Address'>, (1,)) conflicts with
persistent instance <Address at 0x12a25d0>

Warum ist das so? Wir waren bei unseren Kaskaden nicht vorsichtig. Die Zuweisung von a1.user zu einem persistenten Objekt wurde zur Backref von User.addresses kaskadiert und machte unser a1-Objekt pending, als hätten wir es hinzugefügt. Nun haben wir *zwei* Address-Objekte in der Sitzung

>>> a1 = Address()
>>> a1.user = u1
>>> a1 in session
True
>>> existing_a1 in session
True
>>> a1 is existing_a1
False

Oben ist unser a1 bereits pending in der Sitzung. Die anschließende Session.merge()-Operation tut im Wesentlichen nichts. Kaskaden können über die Option relationship.cascade bei relationship() konfiguriert werden, obwohl in diesem Fall die save-update-Kaskade von der User.addresses-Beziehung entfernt werden müsste – und normalerweise ist dieses Verhalten äußerst praktisch. Die Lösung hier wäre normalerweise, a1.user nicht einem Objekt zuzuweisen, das bereits in der Ziel-Sitzung persistent ist.

Die Option cascade_backrefs=False von relationship() verhindert ebenfalls, dass die Address über die Zuweisung a1.user = u1 zur Sitzung hinzugefügt wird.

Weitere Details zur Kaskadenoperation finden Sie unter Kaskaden.

Ein weiteres Beispiel für unerwarteten Zustand

>>> a1 = Address(id=existing_a1.id, user_id=u1.id)
>>> a1.user = None
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.exc.IntegrityError: (IntegrityError) address.user_id
may not be NULL

Oben hat die Zuweisung von user Vorrang vor der Fremdschlüsselzuweisung von user_id, mit dem Endergebnis, dass None auf user_id angewendet wird, was zu einem Fehler führt.

Die meisten Probleme mit Session.merge() können untersucht werden, indem zuerst geprüft wird: Befindet sich das Objekt vorzeitig in der Sitzung?

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> assert a1 not in session
>>> a1 = session.merge(a1)

Oder gibt es einen Zustand im Objekt, den wir nicht wollen? Die Untersuchung von __dict__ ist eine schnelle Möglichkeit, dies zu überprüfen.

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> a1.user
>>> a1.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x1298d10>,
    'user_id': 1,
    'id': 1,
    'user': None}
>>> # we don't want user=None merged, remove it
>>> del a1.user
>>> a1 = session.merge(a1)
>>> # success
>>> session.commit()

Entfernen (Expunging)

Entfernen entfernt ein Objekt aus der Sitzung, sendet persistente Instanzen in den detached-Zustand und pending-Instanzen in den transienten Zustand.

session.expunge(obj1)

Um alle Elemente zu entfernen, rufen Sie Session.expunge_all() auf (diese Methode hieß früher clear()).

Aktualisieren / Verfallen lassen

Verfallen lassen bedeutet, dass die datenbankbeständigen Daten, die in einer Reihe von Objektattributen enthalten sind, gelöscht werden, so dass beim nächsten Zugriff auf diese Attribute eine SQL-Abfrage gesendet wird, die diese Daten aus der Datenbank aktualisiert.

Wenn wir von Verfall von Daten sprechen, meinen wir normalerweise ein Objekt, das sich im persistenten Zustand befindet. Wenn wir beispielsweise ein Objekt wie folgt laden:

user = session.scalars(select(User).filter_by(name="user1").limit(1)).first()

Das obige User-Objekt ist persistent und hat eine Reihe von vorhandenen Attributen; wenn wir in sein __dict__ schauen würden, würden wir sehen, dass dieser Zustand geladen ist.

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

wobei id und name sich auf diese Spalten in der Datenbank beziehen. _sa_instance_state ist ein nicht datenbankbeständiger Wert, der von SQLAlchemy intern verwendet wird (er bezieht sich auf die InstanceState für die Instanz. Obwohl nicht direkt für diesen Abschnitt relevant, sollten wir die Funktion inspect() verwenden, um darauf zuzugreifen).

Zu diesem Zeitpunkt entspricht der Zustand unseres User-Objekts dem der geladenen Datenbankzeile. Aber nach dem Verfallenlassen des Objekts mit einer Methode wie Session.expire() sehen wir, dass der Zustand entfernt wird.

>>> session.expire(user)
>>> user.__dict__
{'_sa_instance_state': <...>}

Wir sehen, dass zwar der interne "Zustand" bestehen bleibt, die Werte, die den Spalten id und name entsprechen, jedoch fehlen. Wenn wir auf eine dieser Spalten zugreifen und SQL beobachten, würden wir Folgendes sehen:

>>> print(user.name)
SELECT user.id AS user_id, user.name AS user_name FROM user WHERE user.id = ? (1,)
user1

Oben, nach dem Zugriff auf das verfallene Attribut user.name, initiierte die ORM einen Lazy Load, um den aktuellsten Zustand aus der Datenbank abzurufen, indem eine SELECT-Anweisung für die Benutzerzeile gesendet wurde, auf die sich dieser Benutzer bezieht. Danach wird das __dict__ erneut gefüllt.

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

Hinweis

Während wir in __dict__ hineinschauen, um zu sehen, was SQLAlchemy mit Objektattributen macht, **sollten wir den Inhalt von** __dict__ **nicht direkt ändern**, zumindest nicht für die Attribute, die von der SQLAlchemy ORM verwaltet werden (andere Attribute außerhalb des Geltungsbereichs von SQLA sind in Ordnung). Das liegt daran, dass SQLAlchemy Deskriptoren verwendet, um die Änderungen zu verfolgen, die wir an einem Objekt vornehmen, und wenn wir __dict__ direkt ändern, kann die ORM nicht verfolgen, dass wir etwas geändert haben.

Ein weiteres wichtiges Verhalten sowohl von Session.expire() als auch von Session.refresh() ist, dass alle ungespülten Änderungen an einem Objekt verworfen werden. Das heißt, wenn wir ein Attribut unseres User modifizieren würden

>>> user.name = "user2"

aber dann Session.expire() aufrufen, ohne zuerst Session.flush() aufzurufen, wird unser pending-Wert von 'user2' verworfen.

>>> session.expire(user)
>>> user.name
'user1'

Die Methode Session.expire() kann verwendet werden, um alle ORM-gemappten Attribute einer Instanz als "verfallen" zu markieren.

# expire all ORM-mapped attributes on obj1
session.expire(obj1)

Sie kann auch eine Liste von Attributnamen als Strings erhalten, die sich auf bestimmte Attribute beziehen, die als verfallen markiert werden sollen.

# expire only attributes obj1.attr1, obj1.attr2
session.expire(obj1, ["attr1", "attr2"])

Die Methode Session.expire_all() ermöglicht es uns, im Wesentlichen Session.expire() auf alle Objekte in der Session auf einmal anzuwenden.

session.expire_all()

Die Methode Session.refresh() hat eine ähnliche Schnittstelle, sendet aber anstelle des Verfallenlassens sofort eine SELECT-Anweisung für die Zeile des Objekts.

# reload all attributes on obj1
session.refresh(obj1)

Session.refresh() akzeptiert auch eine Liste von Attributnamen, erwartet aber im Gegensatz zu Session.expire(), dass mindestens ein Name der Name eines spaltenbasierten Attributs ist.

# reload obj1.attr1, obj1.attr2
session.refresh(obj1, ["attr1", "attr2"])

Tipp

Eine alternative Methode zum Aktualisieren, die oft flexibler ist, ist die Verwendung der Funktion Populate Existing der ORM, die für 2.0-Style-Abfragen mit select() sowie von der Methode Query.populate_existing() von Query in 1.x-Style-Abfragen aus verfügbar ist. Mit dieser Ausführungsoption werden alle ORM-Objekte, die im Ergebnissatz der Anweisung zurückgegeben werden, mit Daten aus der Datenbank aktualisiert.

stmt = (
    select(User)
    .execution_options(populate_existing=True)
    .where((User.name.in_(["a", "b", "c"])))
)
for user in session.execute(stmt).scalars():
    print(user)  # will be refreshed for those columns that came back from the query

Siehe Populate Existing für weitere Details.

Was tatsächlich geladen wird

Die SELECT-Anweisung, die gesendet wird, wenn ein mit Session.expire() markiertes oder mit Session.refresh() geladenes Objekt, variiert je nach mehreren Faktoren, einschließlich

  • Die Ladung verfallener Attribute wird nur von **spaltenbasierten Attributen** ausgelöst. Während jede Art von Attribut als verfallen markiert werden kann, einschließlich eines relationship()-gemappten Attributs, löst der Zugriff auf ein verfallenes relationship()-Attribut nur für dieses Attribut eine Ladung aus, unter Verwendung des standardmäßigen beziehungsbezogenen Lazy Loading. Spaltenorientierte Attribute, selbst wenn sie verfallen sind, werden nicht als Teil dieses Vorgangs geladen, sondern erst, wenn auf ein spaltenorientiertes Attribut zugegriffen wird.

  • relationship()-gemappte Attribute werden nicht als Reaktion auf den Zugriff auf verfallene spaltenbasierte Attribute geladen.

  • In Bezug auf Beziehungen ist Session.refresh() restriktiver als Session.expire() in Bezug auf Attribute, die nicht spaltenbasiert sind. Das Aufrufen von Session.refresh() und die Übergabe einer Liste von Namen, die nur beziehungsbasierte Attribute enthält, führt tatsächlich zu einem Fehler. In jedem Fall werden nicht-eager-loaded relationship()-Attribute in keinen Aktualisierungsvorgang einbezogen.

  • relationship()-Attribute, die über den Parameter relationship.lazy als "eager loading" konfiguriert sind, werden bei Session.refresh() geladen, wenn entweder keine Attributnamen angegeben sind oder wenn ihre Namen in der Liste der zu aktualisierenden Attribute enthalten sind.

  • Attribute, die als deferred() konfiguriert sind, werden normalerweise weder während der Ladung verfallener Attribute noch während eines Aktualisierungsvorgangs geladen. Ein nicht geladenes Attribut, das deferred() ist, wird stattdessen selbst geladen, wenn direkt darauf zugegriffen wird, oder wenn es Teil einer "Gruppe" von deferred-Attributen ist, auf die zugegriffen wird.

  • Für verfallene Attribute, die beim Zugriff geladen werden, sendet eine Joined-Inheritance-Tabellenabbildung eine SELECT-Anweisung, die typischerweise nur die Tabellen enthält, für die nicht geladene Attribute vorhanden sind. Die Aktion hier ist so ausgeklügelt, dass nur die Eltern- oder Kindertabelle geladen wird, zum Beispiel, wenn die Teilmenge der Spalten, die ursprünglich verfallen waren, nur eine oder die andere dieser Tabellen umfasst.

  • Wenn Session.refresh() auf einer Joined-Inheritance-Tabellenabbildung verwendet wird, ähnelt die gesendete SELECT-Anweisung der, die gesendet wird, wenn Session.query() auf die Klasse des Zielobjekts angewendet wird. Dies sind typischerweise alle Tabellen, die als Teil der Abbildung eingerichtet sind.

Wann verfallen lassen oder aktualisieren

Die Session nutzt die Verfallsfunktion automatisch, wann immer die von der Session referenzierte Transaktion endet. Das bedeutet, wann immer Session.commit() oder Session.rollback() aufgerufen wird, werden alle Objekte innerhalb der Session als verfallen markiert, wobei eine Funktion genutzt wird, die der Methode Session.expire_all() entspricht. Die Begründung dafür ist, dass das Ende einer Transaktion ein Abgrenzungspunkt ist, an dem kein weiterer Kontext verfügbar ist, um den aktuellen Zustand der Datenbank zu kennen, da jede Anzahl anderer Transaktionen ihn beeinflussen kann. Erst wenn eine neue Transaktion beginnt, können wir wieder auf den aktuellen Zustand der Datenbank zugreifen, zu welchem Zeitpunkt sich möglicherweise eine beliebige Anzahl von Änderungen ergeben hat.

Die Methoden Session.expire() und Session.refresh() werden in den Fällen verwendet, in denen man ein Objekt zwingen möchte, seine Daten erneut aus der Datenbank zu laden, in Fällen, in denen bekannt ist, dass der aktuelle Datenstand möglicherweise veraltet ist. Gründe dafür können sein:

  • einige SQL-Abfragen wurden innerhalb der Transaktion außerhalb des Geltungsbereichs der ORM-Objektverwaltung ausgegeben, z. B. wenn eine Table.update()-Konstruktion über die Methode Session.execute() ausgegeben wurde;

  • wenn die Anwendung versucht, Daten abzurufen, von denen bekannt ist, dass sie in einer gleichzeitigen Transaktion geändert wurden, und es auch bekannt ist, dass die geltenden Isolationsregeln diese Daten sichtbar machen.

Der zweite Aufzählungspunkt enthält den wichtigen Hinweis, dass "es auch bekannt ist, dass die geltenden Isolationsregeln diese Daten sichtbar machen." Dies bedeutet, dass nicht davon ausgegangen werden kann, dass eine UPDATE-Operation, die auf einer anderen Datenbankverbindung stattgefunden hat, hier lokal sichtbar sein wird; in vielen Fällen wird sie es nicht sein. Deshalb ist ein Verständnis des geltenden Isolationsverhaltens unerlässlich, wenn man Session.expire() oder Session.refresh() verwenden möchte, um Daten zwischen laufenden Transaktionen anzuzeigen.

Siehe auch

Session.expire()

Session.expire_all()

Session.refresh()

Populate Existing - ermöglicht es jeder ORM-Abfrage, Objekte zu aktualisieren, wie sie normalerweise geladen würden, wobei alle übereinstimmenden Objekte in der Identitätszuordnung gegen die Ergebnisse einer SELECT-Anweisung aktualisiert werden.

Isolation - Glossarerklärung der Isolation, die Links zu Wikipedia enthält.

The SQLAlchemy Session In-Depth - ein Video + Folien mit einer ausführlichen Diskussion des Objektlebenszyklus, einschließlich der Rolle der Datenverfalls.