SQLAlchemy 2.0 Dokumentation
Häufig gestellte Fragen
- Installation
- Verbindungen / Engines
- MetaData / Schema
- SQL-Ausdrücke
- ORM-Konfiguration
- Performance
- Sessions / Queries¶
- Ich lade Daten mit meiner Session neu, aber sie sieht keine Änderungen, die ich woanders committet habe.
- „Die Transaktion dieser Session wurde aufgrund einer vorherigen Ausnahme während des Flushens zurückgerollt.“ (oder ähnlich)
- Wie erstelle ich eine Abfrage, die jeder Abfrage immer einen bestimmten Filter hinzufügt?
- Meine Abfrage gibt nicht die gleiche Anzahl von Objekten zurück wie query.count() mir mitteilt - warum?
- Ich habe ein Mapping gegen einen Outer Join erstellt, und obwohl die Abfrage Zeilen zurückgibt, werden keine Objekte zurückgegeben. Warum nicht?
- Ich verwende
joinedload()oderlazy=False, um einen JOIN/OUTER JOIN zu erstellen, und SQLAlchemy konstruiert die falsche Abfrage, wenn ich versuche, ein WHERE, ORDER BY, LIMIT usw. hinzuzufügen (was sich auf den (OUTER) JOIN stützt) - Query hat keine
__len__(), warum nicht? - Wie verwende ich Text-SQL mit ORM-Abfragen?
- Ich rufe
Session.delete(myobject)auf, und es wird nicht aus der übergeordneten Sammlung entfernt! - Warum wird mein
__init__()nicht aufgerufen, wenn ich Objekte lade? - Wie verwende ich ON DELETE CASCADE mit dem ORM von SA?
- Ich habe das Attribut „foo_id“ auf meiner Instanz auf „7“ gesetzt, aber das Attribut „foo“ ist immer noch
None– hätte es nicht Foo mit der ID #7 laden sollen? - Wie durchlaufe ich alle Objekte, die mit einem bestimmten Objekt verknüpft sind?
- Gibt es eine Möglichkeit, automatisch nur eindeutige Schlüsselwörter (oder andere Arten von Objekten) zu haben, ohne eine Abfrage nach dem Schlüsselwort durchzuführen und eine Referenz auf die Zeile zu erhalten, die dieses Schlüsselwort enthält?
- Warum löst post_update zusätzlich zum ersten UPDATE ein UPDATE aus?
- Probleme bei der Integration von Drittanbietern
Projektversionen
- Vorher: Performance
- Nächste: Probleme mit der Integration von Drittanbietern
- Nach oben: Startseite
- Auf dieser Seite
- Sessions / Abfragen
- Ich lade Daten mit meiner Session neu, aber sie sieht keine Änderungen, die ich woanders committet habe.
- „Die Transaktion dieser Session wurde aufgrund einer vorherigen Ausnahme während des Flushens zurückgerollt.“ (oder ähnlich)
- Wie erstelle ich eine Abfrage, die jeder Abfrage immer einen bestimmten Filter hinzufügt?
- Meine Abfrage gibt nicht die gleiche Anzahl von Objekten zurück wie query.count() mir mitteilt - warum?
- Ich habe ein Mapping gegen einen Outer Join erstellt, und obwohl die Abfrage Zeilen zurückgibt, werden keine Objekte zurückgegeben. Warum nicht?
- Ich verwende
joinedload()oderlazy=False, um einen JOIN/OUTER JOIN zu erstellen, und SQLAlchemy konstruiert die falsche Abfrage, wenn ich versuche, ein WHERE, ORDER BY, LIMIT usw. hinzuzufügen (was sich auf den (OUTER) JOIN stützt) - Query hat keine
__len__(), warum nicht? - Wie verwende ich Text-SQL mit ORM-Abfragen?
- Ich rufe
Session.delete(myobject)auf, und es wird nicht aus der übergeordneten Sammlung entfernt! - Warum wird mein
__init__()nicht aufgerufen, wenn ich Objekte lade? - Wie verwende ich ON DELETE CASCADE mit dem ORM von SA?
- Ich habe das Attribut „foo_id“ auf meiner Instanz auf „7“ gesetzt, aber das Attribut „foo“ ist immer noch
None– hätte es nicht Foo mit der ID #7 laden sollen? - Wie durchlaufe ich alle Objekte, die mit einem bestimmten Objekt verknüpft sind?
- Gibt es eine Möglichkeit, automatisch nur eindeutige Schlüsselwörter (oder andere Arten von Objekten) zu haben, ohne eine Abfrage nach dem Schlüsselwort durchzuführen und eine Referenz auf die Zeile zu erhalten, die dieses Schlüsselwort enthält?
- Warum löst post_update zusätzlich zum ersten UPDATE ein UPDATE aus?
Sessions / Queries¶
Ich lade Daten mit meiner Session neu, aber sie sieht keine Änderungen, die ich woanders committet habe¶
Das Hauptproblem bezüglich dieses Verhaltens ist, dass die Session so agiert, als ob die Transaktion den seriellen Isolationszustand hätte, auch wenn dies nicht der Fall ist (und meistens ist es das nicht). Praktisch bedeutet dies, dass die Session keine Daten ändert, die sie bereits innerhalb des Gültigkeitsbereichs einer Transaktion gelesen hat.
Wenn Ihnen der Begriff „Isolationsstufe“ unbekannt ist, müssen Sie zuerst diesen Link lesen
Kurz gesagt bedeutet die serielle Isolationsstufe im Allgemeinen, dass Sie, sobald Sie eine Reihe von Zeilen in einer Transaktion SELECTieren, jedes Mal, wenn Sie diese SELECT-Anweisung erneut ausführen, *dieselbe Daten* zurückerhalten. Wenn Sie sich in der nächstniedrigeren Isolationsstufe, „repeatable read“, befinden, sehen Sie neu hinzugefügte Zeilen (und keine gelöschten Zeilen mehr), aber für Zeilen, die Sie *bereits* geladen haben, sehen Sie keine Änderung. Nur wenn Sie sich in einer niedrigeren Isolationsstufe, z. B. „read committed“, befinden, ist es möglich, dass der Wert einer Datenzeile geändert wird.
Informationen zur Steuerung der Isolationsstufe bei Verwendung des SQLAlchemy ORM finden Sie unter Festlegen von Transaktionsisolationsstufen / DBAPI AUTOCOMMIT.
Um die Dinge dramatisch zu vereinfachen, arbeitet die Session selbst im Rahmen einer vollständig isolierten Transaktion und überschreibt keine zugeordneten Attribute, die sie bereits gelesen hat, es sei denn, Sie weisen sie dazu an. Der Anwendungsfall, Daten neu lesen zu wollen, die Sie in einer laufenden Transaktion bereits geladen haben, ist ein *ungewöhnlicher* Anwendungsfall, der in vielen Fällen keine Auswirkungen hat, daher wird dies als Ausnahme und nicht als Norm betrachtet; um innerhalb dieser Ausnahme zu arbeiten, werden mehrere Methoden bereitgestellt, um das Laden spezifischer Daten innerhalb des Kontexts einer laufenden Transaktion zu ermöglichen.
Um zu verstehen, was wir unter „die Transaktion“ verstehen, wenn wir über die Session sprechen, ist Ihre Session dazu gedacht, nur innerhalb einer Transaktion zu arbeiten. Eine Übersicht hierüber finden Sie unter Verwalten von Transaktionen.
Sobald wir herausgefunden haben, wie unsere Isolationsstufe ist, und wir glauben, dass unsere Isolationsstufe niedrig genug eingestellt ist, sodass wir neue Daten in unserer Session sehen sollten, wenn wir eine Zeile erneut SELECTieren, wie sehen wir sie dann?
Drei Wege, vom häufigsten zum seltensten
Wir beenden einfach unsere Transaktion und starten eine neue bei nächstem Zugriff mit unserer
Session, indem wirSession.commit()aufrufen (beachten Sie, dass bei der weniger gebräuchlichen „Autocommit“-Funktion derSessionauch ein Aufruf vonSession.begin()stattfindet). Die überwiegende Mehrheit der Anwendungen und Anwendungsfälle hat keine Probleme damit, Daten in anderen Transaktionen nicht „sehen“ zu können, da sie diesem Muster folgen, das den Kern der Best Practice von **kurzlebigen Transaktionen** bildet. Siehe Wann erstelle ich eine Session, wann committe ich sie und wann schließe ich sie? für einige Gedanken dazu.Wir weisen unsere
Sessionan, bereits gelesene Zeilen neu zu lesen, entweder wenn wir sie das nächste Mal abfragen, mitSession.expire_all()oderSession.expire(), oder sofort bei einem Objekt mitrefresh. Details hierzu finden Sie unter Aktualisieren / Verwerfen.Wir können ganze Abfragen ausführen und dabei festlegen, dass sie bereits geladene Objekte beim Lesen von Zeilen definitiv überschreiben, indem wir „populate existing“ verwenden. Dies ist eine Ausführungsoption, die unter Populate Existing beschrieben wird.
Aber denken Sie daran: **Das ORM kann keine Änderungen an Zeilen sehen, wenn unsere Isolationsstufe „repeatable read“ oder höher ist, es sei denn, wir starten eine neue Transaktion**.
„Die Transaktion dieser Session wurde aufgrund einer vorherigen Ausnahme während des Flush zurückgerollt.“ (oder ähnlich)¶
Dies ist ein Fehler, der auftritt, wenn eine Session.flush() eine Ausnahme auslöst, die Transaktion zurückrollt, aber weitere Befehle auf der Session ohne expliziten Aufruf von Session.rollback() oder Session.close() aufgerufen werden.
Dies entspricht normalerweise einer Anwendung, die bei Session.flush() oder Session.commit() eine Ausnahme abfängt und die Ausnahme nicht richtig behandelt. Zum Beispiel
from sqlalchemy import create_engine, Column, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(create_engine("sqlite://"))
class Foo(Base):
__tablename__ = "foo"
id = Column(Integer, primary_key=True)
Base.metadata.create_all()
session = sessionmaker()()
# constraint violation
session.add_all([Foo(id=1), Foo(id=1)])
try:
session.commit()
except:
# ignore error
pass
# continue using session without rolling back
session.commit()Die Verwendung der Session sollte in eine Struktur passen, die diesem ähnelt
try:
# <use session>
session.commit()
except:
session.rollback()
raise
finally:
session.close() # optional, depends on use caseViele Dinge können außer Flushes zu einem Fehler innerhalb von try/except führen. Anwendungen sollten sicherstellen, dass ein System von „Framing“ auf ORM-orientierte Prozesse angewendet wird, damit Verbindungs- und Transaktionsressourcen eine definitive Grenze haben und Transaktionen bei Auftreten von Fehlern explizit zurückgerollt werden können.
Dies bedeutet nicht, dass es überall in einer Anwendung try/except-Blöcke geben sollte, was keine skalierbare Architektur wäre. Stattdessen ist ein typischer Ansatz, dass beim ersten Aufruf ORM-orientierter Methoden und Funktionen der Prozess, der die Funktionen von ganz oben aufruft, sich in einem Block befindet, der Transaktionen bei erfolgreichem Abschluss einer Reihe von Operationen committet und Transaktionen zurückrollt, wenn Operationen aus irgendeinem Grund fehlschlagen, einschließlich fehlerhafter Flushes. Es gibt auch Ansätze, die Funktionsdekorationen oder Kontextmanager verwenden, um ähnliche Ergebnisse zu erzielen. Die Art des gewählten Ansatzes hängt stark von der Art der zu schreibenden Anwendung ab.
Für eine detaillierte Diskussion darüber, wie die Verwendung der Session organisiert werden kann, siehe Wann erstelle ich eine Session, wann committe ich sie und wann schließe ich sie?.
Aber warum besteht flush() darauf, ein ROLLBACK auszugeben?¶
Es wäre großartig, wenn Session.flush() teilweise abgeschlossen werden könnte und dann nicht zurückgerollt würde. Dies liegt jedoch außerhalb seiner aktuellen Fähigkeiten, da seine interne Buchführung modifiziert werden müsste, so dass es jederzeit angehalten und exakt mit dem, was auf die Datenbank geschrieben wurde, konsistent sein kann. Obwohl dies theoretisch möglich ist, wird die Nützlichkeit der Verbesserung stark dadurch beeinträchtigt, dass viele Datenbankoperationen ohnehin ein ROLLBACK erfordern. Insbesondere PostgreSQL hat Operationen, die, sobald sie fehlschlagen, die Transaktion nicht fortsetzen können.
test=> create table foo(id integer primary key);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
CREATE TABLE
test=> begin;
BEGIN
test=> insert into foo values(1);
INSERT 0 1
test=> commit;
COMMIT
test=> begin;
BEGIN
test=> insert into foo values(1);
ERROR: duplicate key value violates unique constraint "foo_pkey"
test=> insert into foo values(2);
ERROR: current transaction is aborted, commands ignored until end of transaction blockWas SQLAlchemy bietet, um beide Probleme zu lösen, ist die Unterstützung von SAVEPOINT über Session.begin_nested(). Mit Session.begin_nested() können Sie eine potenziell fehlschlagende Operation in einer Transaktion einrahmen und dann zum Punkt vor ihrem Versagen „zurückrollen“, während die umschließende Transaktion beibehalten wird.
Aber warum reicht der eine automatische Aufruf von ROLLBACK nicht aus? Warum muss ich erneut ROLLBACK durchführen?¶
Das durch flush() verursachte Rollback ist nicht das Ende des vollständigen Transaktionsblocks; während es die Datenbanktransaktion beendet, gibt es aus Sicht der Session immer noch eine Transaktion, die sich nun in einem inaktiven Zustand befindet.
Gegeben sei ein Block wie dieser
sess = Session() # begins a logical transaction
try:
sess.flush()
sess.commit()
except:
sess.rollback()Wenn oben eine Session zum ersten Mal erstellt wird, unter der Annahme, dass „Autocommit-Modus“ nicht verwendet wird, wird innerhalb der Session eine logische Transaktion etabliert. Diese Transaktion ist „logisch“, da sie erst dann Datenbankressourcen nutzt, wenn eine SQL-Anweisung aufgerufen wird, woraufhin eine Transaktion auf Connection- und DBAPI-Ebene gestartet wird. Unabhängig davon, ob Transaktionen auf Datenbankebene Teil ihres Zustands sind oder nicht, bleibt die logische Transaktion bestehen, bis sie mit Session.commit(), Session.rollback() oder Session.close() beendet wird.
Wenn der obige flush() fehlschlägt, befindet sich der Code immer noch innerhalb der Transaktion, die durch den try/commit/except/rollback-Block gerahmt wird. Wenn flush() die logische Transaktion vollständig zurückrollen würde, würde dies bedeuten, dass die Session im except-Block in einem sauberen Zustand wäre, bereit, neue SQLs für eine völlig neue Transaktion auszugeben, und der Aufruf von Session.rollback() wäre nicht mehr korrekt. Insbesondere hätte die Session zu diesem Zeitpunkt eine neue Transaktion begonnen, auf die sich das Session.rollback() fälschlicherweise beziehen würde. Anstatt SQL-Operationen auf einer neuen Transaktion fortzusetzen, an der Stelle, an der der normale Gebrauch ein Rollback vorschreibt, weigert sich die Session, fortzufahren, bis das explizite Rollback tatsächlich stattfindet.
Mit anderen Worten, es wird erwartet, dass der aufrufende Code **immer** Session.commit(), Session.rollback() oder Session.close() aufruft, um den aktuellen Transaktionsblock zu verarbeiten. flush() hält die Session innerhalb dieses Transaktionsblocks, damit das Verhalten des obigen Codes vorhersehbar und konsistent ist.
Wie erstelle ich eine Abfrage, die jeder Abfrage immer einen bestimmten Filter hinzufügt?¶
Siehe das Rezept unter FilteredQuery.
Meine Abfrage gibt nicht die gleiche Anzahl von Objekten zurück, wie query.count() mir sagt – warum?¶
Das Query-Objekt dedupliziert die Objekte anhand des Primärschlüssels, wenn es angewiesen wird, eine Liste von ORM-zugeordneten Objekten zurückzugeben. Das heißt, wenn wir zum Beispiel das unter Verwenden von ORM-Deklarativen Formularen zur Definition von Tabellenmetadaten beschriebene User-Mapping verwenden und eine SQL-Abfrage wie die folgende hätten
q = session.query(User).outerjoin(User.addresses).filter(User.name == "jack")Die im Tutorial verwendeten Beispieldaten enthalten zwei Zeilen in der addresses-Tabelle für die users-Zeile mit dem Namen 'jack', Primärschlüsselwert 5. Wenn wir für die obige Abfrage Query.count() aufrufen, erhalten wir die Antwort **2**
>>> q.count()
2Wenn wir jedoch Query.all() ausführen oder über die Abfrage iterieren, erhalten wir **ein Element** zurück
>>> q.all()
[User(id=5, name='jack', ...)]Dies liegt daran, dass, wenn das Query-Objekt vollständige Entitäten zurückgibt, diese **dedupliziert** werden. Dies geschieht nicht, wenn wir stattdessen einzelne Spalten anfordern
>>> session.query(User.id, User.name).outerjoin(User.addresses).filter(
... User.name == "jack"
... ).all()
[(5, 'jack'), (5, 'jack')]Es gibt zwei Hauptgründe für die Deduplizierung durch die Query
Damit das joined eager loading korrekt funktioniert – Joined Eager Loading funktioniert, indem Zeilen mithilfe von Joins gegen verknüpfte Tabellen abgefragt werden, wobei Zeilen aus diesen Joins dann in Sammlungen auf den führenden Objekten geroutet werden. Um dies zu tun, müssen Zeilen abgerufen werden, bei denen der Primärschlüssel des führenden Objekts für jeden Untereintrag wiederholt wird. Dieses Muster kann sich dann in weitere Unter-Sammlungen fortsetzen, sodass für ein einzelnes führendes Objekt ein Vielfaches von Zeilen verarbeitet werden kann, z. B.
User(id=5). Die Deduplizierung ermöglicht es uns, Objekte so zu erhalten, wie sie abgefragt wurden, z. B. alleUser()-Objekte, deren Name'jack'ist, was für uns ein Objekt ist, mit derUser.addresses-Sammlung, die entweder durchlazy='joined'auf demrelationship()oder über diejoinedload()-Option angegeben wurde, eager geladen wurde. Aus Konsistenzgründen wird die Deduplizierung trotzdem angewendet, unabhängig davon, ob joinedload eingerichtet ist, da die Kernphilosophie des eager loading darin besteht, dass diese Optionen niemals die Ergebnisse beeinflussen.Um Verwirrung bezüglich der Identitäts-Map zu vermeiden – dies ist zugegebenermaßen der weniger kritische Grund. Da die
Sessioneine Identitäts-Map verwendet, gibt es, obwohl unser SQL-Ergebnis-Set zwei Zeilen mit dem Primärschlüssel 5 hat, nur einUser(id=5)-Objekt innerhalb derSession, das eindeutig anhand seiner Identität, d. h. der Kombination aus Primärschlüssel/Klasse, beibehalten werden muss. Es ergibt nicht wirklich viel Sinn, wenn man nachUser()-Objekten abfragt, dass man dasselbe Objekt mehrmals in der Liste erhält. Eine geordnete Menge wäre möglicherweise eine bessere Darstellung dessen, wasQueryzurückgeben möchte, wenn es vollständige Objekte zurückgibt.
Das Problem der Deduplizierung von Query bleibt problematisch, hauptsächlich aus dem einzigen Grund, dass die Methode Query.count() inkonsistent ist, und der aktuelle Status ist, dass joined eager loading in früheren Versionen zuerst durch die Strategie „subquery eager loading“ und neuerdings durch die Strategie „select IN eager loading“ abgelöst wurde, die beide generell besser für die Sammlung von eager loading geeignet sind. Da diese Entwicklung fortschreitet, kann SQLAlchemy dieses Verhalten bei Query ändern, was auch neue APIs beinhalten kann, um dieses Verhalten direkter zu steuern, und auch das Verhalten von joined eager loading ändern kann, um ein konsistenteres Nutzungsmuster zu schaffen.
Ich habe ein Mapping gegen einen Outer Join erstellt, und obwohl die Abfrage Zeilen zurückgibt, werden keine Objekte zurückgegeben. Warum nicht?¶
Zeilen, die von einem Outer Join zurückgegeben werden, können für einen Teil des Primärschlüssels NULL enthalten, da der Primärschlüssel eine Kombination aus beiden Tabellen ist. Das Query-Objekt ignoriert eingehende Zeilen, die keinen akzeptablen Primärschlüssel haben. Basierend auf der Einstellung des Flags allow_partial_pks auf Mapper wird ein Primärschlüssel akzeptiert, wenn der Wert mindestens einen Nicht-NULL-Wert aufweist, oder alternativ, wenn der Wert keine NULL-Werte hat. Siehe allow_partial_pks bei Mapper.
Ich verwende joinedload() oder lazy=False, um einen JOIN/OUTER JOIN zu erstellen, und SQLAlchemy konstruiert die falsche Abfrage, wenn ich versuche, ein WHERE, ORDER BY, LIMIT usw. hinzuzufügen (was sich auf den (OUTER) JOIN stützt)¶
Die von joined eager loading generierten Joins werden nur verwendet, um verwandte Sammlungen vollständig zu laden, und sind so konzipiert, dass sie keine Auswirkungen auf die primären Ergebnisse der Abfrage haben. Da sie anonym mit Aliassen versehen sind, können sie nicht direkt referenziert werden.
Details zu diesem Verhalten finden Sie unter Der Zen des Joined Eager Loading.
Query hat keine __len__(), warum nicht?¶
Die Python-Magic-Methode __len__(), die auf ein Objekt angewendet wird, ermöglicht die Verwendung der integrierten Funktion len() zur Bestimmung der Länge der Sammlung. Es ist naheliegend, dass ein SQL-Abfrageobjekt __len__() mit der Methode Query.count() verknüpft, die ein SELECT COUNT ausgibt. Der Grund, warum dies nicht möglich ist, liegt darin, dass die Auswertung der Abfrage als Liste zwei SQL-Aufrufe anstelle von einem verursachen würde
class Iterates:
def __len__(self):
print("LEN!")
return 5
def __iter__(self):
print("ITER!")
return iter([1, 2, 3, 4, 5])
list(Iterates())Ausgabe
ITER!
LEN!Wie verwende ich Text-SQL mit ORM-Abfragen?¶
Siehe
ORM-Ergebnisse aus Textual Statements abrufen – Ad-hoc-Textblöcke mit
QuerySQL-Ausdrücke mit Sessions verwenden – Verwendung von
Sessionmit direktem Text-SQL.
Ich rufe Session.delete(myobject) auf, und es wird nicht aus der übergeordneten Sammlung entfernt!¶
Siehe Hinweise zum Löschen – Objekte löschen, die aus Sammlungen und Skalarbeziehungen referenziert werden für eine Beschreibung dieses Verhaltens.
Warum wird mein __init__() nicht aufgerufen, wenn ich Objekte lade?¶
Siehe Nicht-zugeordnete Zustände bei Ladevorgängen beibehalten für eine Beschreibung dieses Verhaltens.
Wie verwende ich ON DELETE CASCADE mit dem ORM von SA?¶
SQLAlchemy gibt immer UPDATE- oder DELETE-Anweisungen für abhängige Zeilen aus, die sich derzeit in der Session befinden. Für nicht geladene Zeilen gibt es standardmäßig SELECT-Anweisungen aus, um diese Zeilen zu laden und zu aktualisieren/löschen; mit anderen Worten, es wird angenommen, dass kein ON DELETE CASCADE konfiguriert ist. Um SQLAlchemy zur Zusammenarbeit mit ON DELETE CASCADE zu konfigurieren, siehe Verwenden von ON DELETE cascade mit Fremdschlüsseln für ORM-Beziehungen.
Ich habe das Attribut „foo_id“ auf meiner Instanz auf „7“ gesetzt, aber das Attribut „foo“ ist immer noch None – hätte es nicht Foo mit der ID #7 laden sollen?¶
Das ORM ist nicht so aufgebaut, dass es eine sofortige Befüllung von Beziehungen unterstützt, die durch Änderungen von Fremdschlüsselattributen ausgelöst werden – stattdessen ist es so konzipiert, dass es umgekehrt funktioniert – Fremdschlüsselattribute werden vom ORM im Hintergrund behandelt, der Endbenutzer richtet Objektbeziehungen natürlich ein. Daher ist die empfohlene Methode zum Setzen von o.foo, genau das zu tun – es zu setzen!
foo = session.get(Foo, 7)
o.foo = foo
Session.commit()Die Manipulation von Fremdschlüsselattributen ist natürlich völlig legal. Das Setzen eines Fremdschlüsselattributs auf einen neuen Wert löst derzeit jedoch kein „Expire“-Ereignis für die relationship() aus, an der es beteiligt ist. Das bedeutet, dass für die folgende Sequenz
o = session.scalars(select(SomeClass).limit(1)).first()
# assume the existing o.foo_id value is None;
# accessing o.foo will reconcile this as ``None``, but will effectively
# "load" the value of None
assert o.foo is None
# now set foo_id to something. o.foo will not be immediately affected
o.foo_id = 7o.foo wird beim ersten Zugriff mit seinem effektiven Datenbankwert von None geladen. Das Setzen von o.foo_id = 7 hat den Wert „7“ als ausstehende Änderung, aber kein Flush wurde durchgeführt – also ist o.foo immer noch None
# attribute is already "loaded" as None, has not been
# reconciled with o.foo_id = 7 yet
assert o.foo is NoneDamit o.foo basierend auf der Fremdschlüssel-Mutation geladen wird, geschieht dies normalerweise natürlich nach dem Commit, der sowohl den neuen Fremdschlüsselwert leert als auch den gesamten Zustand ungültig macht.
session.commit() # expires all attributes
foo_7 = session.get(Foo, 7)
# o.foo will lazyload again, this time getting the new object
assert o.foo is foo_7Eine minimalere Operation ist das einzelne Ungültigmachen des Attributs – dies kann für jedes persistente Objekt mittels Session.expire() durchgeführt werden.
o = session.scalars(select(SomeClass).limit(1)).first()
o.foo_id = 7
Session.expire(o, ["foo"]) # object must be persistent for this
foo_7 = session.get(Foo, 7)
assert o.foo is foo_7 # o.foo lazyloads on accessBeachten Sie, dass wenn das Objekt nicht persistent, aber in der Session vorhanden ist, es als ausstehend bezeichnet wird. Das bedeutet, dass die Zeile für das Objekt noch nicht in die Datenbank eingefügt wurde. Für ein solches Objekt hat das Setzen von foo_id keine Bedeutung, bis die Zeile eingefügt ist; andernfalls existiert noch keine Zeile.
new_obj = SomeClass()
new_obj.foo_id = 7
Session.add(new_obj)
# returns None but this is not a "lazyload", as the object is not
# persistent in the DB yet, and the None value is not part of the
# object's state
assert new_obj.foo is None
Session.flush() # emits INSERT
assert new_obj.foo is foo_7 # now it loadsDas Rezept ExpireRelationshipOnFKChange enthält ein Beispiel, das SQLAlchemy-Ereignisse verwendet, um das Setzen von Fremdschlüsselattributen mit Many-to-One-Beziehungen zu koordinieren.
Wie kann ich alle Objekte durchlaufen, die mit einem gegebenen Objekt verknüpft sind?¶
Ein Objekt, das andere damit verknüpfte Objekte hat, entspricht den zwischen den Mappern eingerichteten relationship()-Konstrukten. Dieses Code-Fragment durchläuft alle Objekte und korrigiert dabei auch Zyklen.
from sqlalchemy import inspect
def walk(obj):
deque = [obj]
seen = set()
while deque:
obj = deque.pop(0)
if obj in seen:
continue
else:
seen.add(obj)
yield obj
insp = inspect(obj)
for relationship in insp.mapper.relationships:
related = getattr(obj, relationship.key)
if relationship.uselist:
deque.extend(related)
elif related is not None:
deque.append(related)Die Funktion kann wie folgt demonstriert werden:
Base = declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
bs = relationship("B", backref="a")
class B(Base):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey("a.id"))
c_id = Column(ForeignKey("c.id"))
c = relationship("C", backref="bs")
class C(Base):
__tablename__ = "c"
id = Column(Integer, primary_key=True)
a1 = A(bs=[B(), B(c=C())])
for obj in walk(a1):
print(obj)Ausgabe
<__main__.A object at 0x10303b190>
<__main__.B object at 0x103025210>
<__main__.B object at 0x10303b0d0>
<__main__.C object at 0x103025490>Gibt es eine Möglichkeit, automatisch nur eindeutige Schlüsselwörter (oder andere Arten von Objekten) zu haben, ohne eine Abfrage für das Schlüsselwort durchzuführen und eine Referenz auf die Zeile zu erhalten, die dieses Schlüsselwort enthält?¶
Wenn Leute das Many-to-Many-Beispiel in der Dokumentation lesen, stellen sie fest, dass wenn sie dasselbe Keyword zweimal erstellen, es auch zweimal in die DB eingefügt wird. Was etwas unpraktisch ist.
Dieses Rezept UniqueObject wurde erstellt, um dieses Problem zu lösen.
Warum gibt `post_update` neben dem ersten UPDATE noch ein weiteres UPDATE aus?¶
Das `post_update`-Feature, dokumentiert unter Zeilen, die auf sich selbst verweisen / Wechselseitig abhängige Zeilen, beinhaltet, dass eine UPDATE-Anweisung als Reaktion auf Änderungen an einem bestimmten beziehungsgebundenen Fremdschlüssel ausgegeben wird, zusätzlich zu dem INSERT/UPDATE/DELETE, das normalerweise für die Zielzeile ausgegeben würde. Während der Hauptzweck dieser UPDATE-Anweisung darin besteht, sie mit einem INSERT oder DELETE dieser Zeile zu koppeln, um einen Zyklus mit einem wechselseitig abhängigen Fremdschlüssel zu brechen, wird sie derzeit auch als zweites UPDATE gebündelt, das ausgegeben wird, wenn die Zielzeile selbst einem UPDATE unterliegt. In diesem Fall ist die von `post_update` ausgegebene UPDATE-Anweisung **normalerweise unnötig** und erscheint oft verschwenderisch.
Einige Nachforschungen, um dieses „UPDATE / UPDATE“-Verhalten zu entfernen, zeigen jedoch, dass erhebliche Änderungen am Unit-of-Work-Prozess erforderlich wären, nicht nur in der `post_update`-Implementierung, sondern auch in Bereichen, die nichts mit `post_update` zu tun haben. Dies liegt daran, dass die Reihenfolge der Operationen auf der Nicht-`post_update`-Seite in einigen Fällen umgekehrt werden müsste, was wiederum andere Fälle beeinträchtigen kann, wie z. B. die korrekte Behandlung eines UPDATEs eines referenzierten Primärschlüsselwerts (siehe #1063 für einen Proof of Concept).
Die Antwort ist, dass „post_update“ verwendet wird, um einen Zyklus zwischen zwei wechselseitig abhängigen Fremdschlüsseln zu brechen, und damit dieses Zyklusbrechen nur auf INSERT/DELETE der Zieltabelle beschränkt ist, müsste die Reihenfolge der UPDATE-Anweisungen anderswo liberalisiert werden, was zu Fehlern in anderen Randfällen führt.
Die Designs von flambé! dem Drachen und Der Alchemist wurden von Rotem Yaari erstellt und großzügig gespendet.
Erstellt mit Sphinx 7.2.6. Dokumentation zuletzt generiert: Di 11 Mär 2025 14:40:17 EDT