Arbeiten mit großen Sammlungen

Das Standardverhalten von relationship() besteht darin, die Inhalte von Sammlungen vollständig in den Speicher zu laden, basierend auf einer konfigurierten Loader-Strategie, die steuert, wann und wie diese Inhalte aus der Datenbank geladen werden. Verbundene Sammlungen können nicht nur beim Zugriff oder beim Eager-Loading in den Speicher geladen werden, sondern erfordern in den meisten Fällen eine Popolazione, wenn die Sammlung selbst mutiert wird, sowie in Fällen, in denen das besitzende Objekt vom Unit-of-Work-System gelöscht werden soll.

Wenn eine verbundene Sammlung potenziell sehr groß ist, ist es möglicherweise nicht machbar, dass eine solche Sammlung unter keinen Umständen in den Speicher geladen wird, da die Operation zu zeit-, netzwerk- und speicherressourcenintensiv sein kann.

Dieser Abschnitt enthält API-Funktionen, die es ermöglichen, relationship() mit großen Sammlungen zu verwenden und gleichzeitig eine angemessene Leistung beizubehalten.

Schreibgeschützte Beziehungen

Die schreibgeschützte Loader-Strategie ist das primäre Mittel zur Konfiguration einer relationship(), die schreibbar bleibt, aber ihre Inhalte nicht in den Speicher lädt. Eine schreibgeschützte ORM-Konfiguration im modernen typsicheren Deklarativformat ist unten dargestellt

>>> from decimal import Decimal
>>> from datetime import datetime

>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy import func
>>> from sqlalchemy.orm import DeclarativeBase
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> from sqlalchemy.orm import Session
>>> from sqlalchemy.orm import WriteOnlyMapped

>>> class Base(DeclarativeBase):
...     pass

>>> class Account(Base):
...     __tablename__ = "account"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     identifier: Mapped[str]
...
...     account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
...         cascade="all, delete-orphan",
...         passive_deletes=True,
...         order_by="AccountTransaction.timestamp",
...     )
...
...     def __repr__(self):
...         return f"Account(identifier={self.identifier!r})"

>>> class AccountTransaction(Base):
...     __tablename__ = "account_transaction"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     account_id: Mapped[int] = mapped_column(
...         ForeignKey("account.id", ondelete="cascade")
...     )
...     description: Mapped[str]
...     amount: Mapped[Decimal]
...     timestamp: Mapped[datetime] = mapped_column(default=func.now())
...
...     def __repr__(self):
...         return (
...             f"AccountTransaction(amount={self.amount:.2f}, "
...             f"timestamp={self.timestamp.isoformat()!r})"
...         )
...
...     __mapper_args__ = {"eager_defaults": True}

Oben ist die account_transactions-Beziehung nicht mit der gewöhnlichen Mapped-Annotation konfiguriert, sondern stattdessen mit der WriteOnlyMapped-Typannotation, die zur Laufzeit die Loader-Strategie lazy="write_only" der Ziel- relationship() zuweist. Die WriteOnlyMapped-Annotation ist eine alternative Form der Mapped-Annotation, die die Verwendung des WriteOnlyCollection-Sammlungstyps auf Instanzen des Objekts anzeigt.

Die obige relationship()-Konfiguration enthält auch mehrere Elemente, die spezifisch für die Aktion sind, die beim Löschen von Account-Objekten sowie beim Entfernen von AccountTransaction-Objekten aus der account_transactions-Sammlung ausgeführt werden soll. Diese Elemente sind

Neu in Version 2.0: Hinzugefügte "Write only"-Beziehungs-Loader.

Erstellen und Persistieren neuer schreibgeschützter Sammlungen

Die schreibgeschützte Sammlung ermöglicht die direkte Zuweisung der Sammlung als Ganzes nur für transiente oder ausstehende Objekte. Mit unserer obigen Abbildung können wir ein neues Account-Objekt mit einer Sequenz von AccountTransaction-Objekten erstellen, die zu einer Session hinzugefügt werden sollen. Jede Python-Iterable kann als Quelle für Objekte verwendet werden, um zu beginnen, wobei wir unten eine Python- list verwenden

>>> new_account = Account(
...     identifier="account_01",
...     account_transactions=[
...         AccountTransaction(description="initial deposit", amount=Decimal("500.00")),
...         AccountTransaction(description="transfer", amount=Decimal("1000.00")),
...         AccountTransaction(description="withdrawal", amount=Decimal("-29.50")),
...     ],
... )

>>> with Session(engine) as session:
...     session.add(new_account)
...     session.commit()
BEGIN (implicit) INSERT INTO account (identifier) VALUES (?) [...] ('account_01',) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [... (insertmanyvalues) 1/3 (ordered; batch not supported)] (1, 'initial deposit', 500.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 2/3 (ordered; batch not supported)] (1, 'transfer', 1000.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 3/3 (ordered; batch not supported)] (1, 'withdrawal', -29.5) COMMIT

Sobald ein Objekt in der Datenbank persistiert ist (d.h. im persistenten oder abgetrennten Zustand), kann die Sammlung mit neuen Elementen erweitert werden, ebenso wie einzelne Elemente entfernt werden können. Die Sammlung kann jedoch nicht mehr mit einer vollständigen Ersatzsammlung neu zugewiesen werden, da eine solche Operation erfordert, dass die alte Sammlung vollständig in den Speicher geladen wird, um die alten Einträge mit den neuen abzugleichen.

>>> new_account.account_transactions = [
...     AccountTransaction(description="some transaction", amount=Decimal("10.00"))
... ]
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: Collection "Account.account_transactions" does not
support implicit iteration; collection replacement operations can't be used

Hinzufügen neuer Elemente zu einer bestehenden Sammlung

Für schreibgeschützte Sammlungen von persistenten Objekten können Modifikationen an der Sammlung mittels Unit-of-Work-Prozessen nur durch die Verwendung der Methoden WriteOnlyCollection.add(), WriteOnlyCollection.add_all() und WriteOnlyCollection.remove() erfolgen.

>>> from sqlalchemy import select
>>> session = Session(engine, expire_on_commit=False)
>>> existing_account = session.scalar(select(Account).filter_by(identifier="account_01"))
BEGIN (implicit) SELECT account.id, account.identifier FROM account WHERE account.identifier = ? [...] ('account_01',)
>>> existing_account.account_transactions.add_all( ... [ ... AccountTransaction(description="paycheck", amount=Decimal("2000.00")), ... AccountTransaction(description="rent", amount=Decimal("-800.00")), ... ] ... ) >>> session.commit()
INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [... (insertmanyvalues) 1/2 (ordered; batch not supported)] (1, 'paycheck', 2000.0) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp [insertmanyvalues 2/2 (ordered; batch not supported)] (1, 'rent', -800.0) COMMIT

Die oben hinzugefügten Elemente werden in einer Pending-Warteschlange innerhalb der Session gehalten, bis zum nächsten Flush, an dem sie in die Datenbank eingefügt werden, vorausgesetzt, die hinzugefügten Objekte waren zuvor transient.

Abfragen von Elementen

Die WriteOnlyCollection speichert zu keinem Zeitpunkt eine Referenz auf den aktuellen Inhalt der Sammlung, noch hat sie ein Verhalten, bei dem sie direkt eine SELECT-Anweisung an die Datenbank senden würde, um sie zu laden; die übergeordnete Annahme ist, dass die Sammlung Tausende oder Millionen von Zeilen enthalten kann und niemals vollständig in den Speicher geladen werden sollte, als Nebeneffekt einer anderen Operation.

Stattdessen enthält die WriteOnlyCollection SQL-generierende Helfer wie WriteOnlyCollection.select(), die einen Select-Konstrukt generieren, das mit den korrekten WHERE/FROM-Kriterien für die aktuelle Elternzeile vorab konfiguriert ist, was weiter modifiziert werden kann, um jeden gewünschten Bereich von Zeilen auszuwählen, sowie unter Verwendung von Funktionen wie Server-seitige Cursors für Prozesse, die die gesamte Sammlung speichereffizient durchlaufen möchten.

Die generierte Anweisung ist unten dargestellt. Beachten Sie, dass sie auch ORDER BY-Kriterien enthält, die in der Beispielabbildung durch den Parameter relationship.order_by von relationship() angegeben sind; dieses Kriterium würde weggelassen, wenn der Parameter nicht konfiguriert wäre.

>>> print(existing_account.account_transactions.select())
SELECT account_transaction.id, account_transaction.account_id, account_transaction.description, account_transaction.amount, account_transaction.timestamp FROM account_transaction WHERE :param_1 = account_transaction.account_id ORDER BY account_transaction.timestamp

Wir können dieses Select-Konstrukt zusammen mit der Session verwenden, um nach AccountTransaction-Objekten abzufragen, am einfachsten mit der Methode Session.scalars(), die ein Result zurückgibt, das direkt ORM-Objekte liefert. Es ist üblich, aber nicht zwingend erforderlich, dass die Select weiter modifiziert wird, um die zurückgegebenen Datensätze zu begrenzen; im folgenden Beispiel werden zusätzliche WHERE-Kriterien hinzugefügt, um nur "Debit"-Konto-Transaktionen zu laden, zusammen mit "LIMIT 10", um nur die ersten zehn Zeilen abzurufen.

>>> account_transactions = session.scalars(
...     existing_account.account_transactions.select()
...     .where(AccountTransaction.amount < 0)
...     .limit(10)
... ).all()
BEGIN (implicit) SELECT account_transaction.id, account_transaction.account_id, account_transaction.description, account_transaction.amount, account_transaction.timestamp FROM account_transaction WHERE ? = account_transaction.account_id AND account_transaction.amount < ? ORDER BY account_transaction.timestamp LIMIT ? OFFSET ? [...] (1, 0, 10, 0)
>>> print(account_transactions) [AccountTransaction(amount=-29.50, timestamp='...'), AccountTransaction(amount=-800.00, timestamp='...')]

Entfernen von Elementen

Einzelne Elemente, die im persistenten Zustand gegen die aktuelle Session geladen werden, können mit der Methode WriteOnlyCollection.remove() zum Entfernen aus der Sammlung markiert werden. Der Flush-Prozess wird das Objekt implizit als bereits Teil der Sammlung betrachten, wenn die Operation ausgeführt wird. Das folgende Beispiel zeigt die Entfernung eines einzelnen AccountTransaction-Elements, was gemäß den Cascade-Einstellungen zu einem DELETE dieser Zeile führt.

>>> existing_transaction = account_transactions[0]
>>> existing_account.account_transactions.remove(existing_transaction)
>>> session.commit()
DELETE FROM account_transaction WHERE account_transaction.id = ? [...] (3,) COMMIT

Wie bei jeder ORM-abgebildeten Sammlung kann die Objektentfernung entweder dazu dienen, das Objekt von der Sammlung zu trennen und das Objekt in der Datenbank zu belassen, oder eine DELETE-Operation für seine Zeile auszulösen, basierend auf der delete-orphan-Konfiguration der relationship().

Die Entfernung einer Sammlung ohne Löschung beinhaltet das Setzen von Fremdschlüsselspalten auf NULL für eine One-to-Many-Beziehung oder das Löschen der entsprechenden Verknüpfungszeile für eine Many-to-Many-Beziehung.

Massen-INSERT neuer Elemente

Die WriteOnlyCollection kann DML-Konstrukte wie Insert-Objekte generieren, die in einem ORM-Kontext verwendet werden können, um Massen-Insert-Verhalten zu erzeugen. Siehe den Abschnitt ORM Massen-INSERT-Anweisungen für einen Überblick über ORM-Massen-Inserts.

One-to-Many-Sammlungen

Nur für eine normale One-to-Many-Sammlung generiert die Methode WriteOnlyCollection.insert() ein Insert-Konstrukt, das mit VALUES-Kriterien, die dem Elternobjekt entsprechen, vorab festgelegt ist. Da diese VALUES-Kriterien vollständig gegen die zugehörige Tabelle gerichtet sind, kann die Anweisung verwendet werden, um neue Zeilen einzufügen, die gleichzeitig neue Datensätze in der zugehörigen Sammlung werden.

>>> session.execute(
...     existing_account.account_transactions.insert(),
...     [
...         {"description": "transaction 1", "amount": Decimal("47.50")},
...         {"description": "transaction 2", "amount": Decimal("-501.25")},
...         {"description": "transaction 3", "amount": Decimal("1800.00")},
...         {"description": "transaction 4", "amount": Decimal("-300.00")},
...     ],
... )
BEGIN (implicit) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP) [...] [(1, 'transaction 1', 47.5), (1, 'transaction 2', -501.25), (1, 'transaction 3', 1800.0), (1, 'transaction 4', -300.0)] <...>
>>> session.commit() COMMIT

Many-to-Many-Sammlungen

Bei einer Many-to-Many-Sammlung beinhaltet die Beziehung zwischen zwei Klassen eine dritte Tabelle, die mit dem Parameter relationship.secondary von relationship konfiguriert wird. Um Zeilen in einer Sammlung dieses Typs mit WriteOnlyCollection als Masse einzufügen, können die neuen Datensätze zuerst separat als Masse eingefügt, mit RETURNING abgerufen und dann der Methode WriteOnlyCollection.add_all() übergeben werden, wo der Unit-of-Work-Prozess sie als Teil der Sammlung persistieren wird.

Angenommen, eine Klasse BankAudit bezog sich über eine Many-to-Many-Tabelle auf viele AccountTransaction-Datensätze.

>>> from sqlalchemy import Table, Column
>>> audit_to_transaction = Table(
...     "audit_transaction",
...     Base.metadata,
...     Column("audit_id", ForeignKey("audit.id", ondelete="CASCADE"), primary_key=True),
...     Column(
...         "transaction_id",
...         ForeignKey("account_transaction.id", ondelete="CASCADE"),
...         primary_key=True,
...     ),
... )
>>> class BankAudit(Base):
...     __tablename__ = "audit"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
...         secondary=audit_to_transaction, passive_deletes=True
...     )

Um die beiden Operationen zu veranschaulichen, fügen wir weitere AccountTransaction-Objekte per Massen-Insert hinzu, die wir mit RETURNING abrufen, indem wir returning(AccountTransaction) zur Massen-INSERT-Anweisung hinzufügen (beachten Sie, dass wir auch vorhandene AccountTransaction-Objekte verwenden könnten).

>>> new_transactions = session.scalars(
...     existing_account.account_transactions.insert().returning(AccountTransaction),
...     [
...         {"description": "odd trans 1", "amount": Decimal("50000.00")},
...         {"description": "odd trans 2", "amount": Decimal("25000.00")},
...         {"description": "odd trans 3", "amount": Decimal("45.00")},
...     ],
... ).all()
BEGIN (implicit) INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, account_id, description, amount, timestamp [...] (1, 'odd trans 1', 50000.0, 1, 'odd trans 2', 25000.0, 1, 'odd trans 3', 45.0)

Mit einer Liste von AccountTransaction-Objekten bereit, wird die Methode WriteOnlyCollection.add_all() verwendet, um viele Zeilen auf einmal mit einem neuen BankAudit-Objekt zu verknüpfen.

>>> bank_audit = BankAudit()
>>> session.add(bank_audit)
>>> bank_audit.account_transactions.add_all(new_transactions)
>>> session.commit()
INSERT INTO audit DEFAULT VALUES [...] () INSERT INTO audit_transaction (audit_id, transaction_id) VALUES (?, ?) [...] [(1, 10), (1, 11), (1, 12)] COMMIT

Massen-UPDATE und -DELETE von Elementen

Auf ähnliche Weise, wie WriteOnlyCollection Select-Konstrukte mit vorab festgelegten WHERE-Kriterien generieren kann, kann sie auch Update- und Delete-Konstrukte mit denselben WHERE-Kriterien generieren, um Kriterien-orientierte UPDATE- und DELETE-Anweisungen gegen die Elemente einer großen Sammlung zu ermöglichen.

One-to-Many-Sammlungen

Wie bei INSERT ist diese Funktion am einfachsten mit One-to-Many-Sammlungen.

Im folgenden Beispiel wird die Methode WriteOnlyCollection.update() verwendet, um eine UPDATE-Anweisung zu generieren, die gegen die Elemente in der Sammlung ausgegeben wird, wobei Zeilen gefunden werden, bei denen der "Betrag" gleich -800 ist und ihnen ein Betrag von 200 hinzugefügt wird.

>>> session.execute(
...     existing_account.account_transactions.update()
...     .values(amount=AccountTransaction.amount + 200)
...     .where(AccountTransaction.amount == -800),
... )
BEGIN (implicit) UPDATE account_transaction SET amount=(account_transaction.amount + ?) WHERE ? = account_transaction.account_id AND account_transaction.amount = ? [...] (200, 1, -800)
<...>

Auf ähnliche Weise wird WriteOnlyCollection.delete() eine DELETE-Anweisung erzeugen, die auf die gleiche Weise aufgerufen wird.

>>> session.execute(
...     existing_account.account_transactions.delete().where(
...         AccountTransaction.amount.between(0, 30)
...     ),
... )
DELETE FROM account_transaction WHERE ? = account_transaction.account_id AND account_transaction.amount BETWEEN ? AND ? RETURNING id [...] (1, 0, 30) <...>

Many-to-Many-Sammlungen

Tipp

Die Techniken hier beinhalten Multi-Table-UPDATE-Ausdrücke, die etwas fortgeschrittener sind.

Für Massen-UPDATEs und -DELETEs von Many-to-Many-Sammlungen muss die Verknüpfungstabelle explizit Teil der UPDATE/DELETE-Anweisung sein, damit eine UPDATE- oder DELETE-Anweisung sich auf den Primärschlüssel des Elternobjekts bezieht, was entweder erfordert, dass das Backend nicht standardmäßige SQL-Syntax unterstützt, oder zusätzliche explizite Schritte bei der Erstellung der UPDATE- oder DELETE-Anweisung.

Für Backends, die Multi-Table-Versionen von UPDATE unterstützen, sollte die Methode WriteOnlyCollection.update() ohne zusätzliche Schritte für eine Many-to-Many-Sammlung funktionieren, wie im folgenden Beispiel, wo ein UPDATE im Hinblick auf die Many-to-Many-Sammlung BankAudit.account_transactions gegen AccountTransaction-Objekte ausgegeben wird.

>>> session.execute(
...     bank_audit.account_transactions.update().values(
...         description=AccountTransaction.description + " (audited)"
...     )
... )
UPDATE account_transaction SET description=(account_transaction.description || ?) FROM audit_transaction WHERE ? = audit_transaction.audit_id AND account_transaction.id = audit_transaction.transaction_id RETURNING id [...] (' (audited)', 1)
<...>

Die obige Anweisung verwendet automatisch die "UPDATE..FROM"-Syntax, die von SQLite und anderen unterstützt wird, um die zusätzliche audit_transaction-Tabelle in der WHERE-Klausel zu benennen.

Um eine Many-to-Many-Sammlung zu aktualisieren oder zu löschen, wo keine Multi-Table-Syntax verfügbar ist, können die Many-to-Many-Kriterien in eine SELECT-Anweisung verschoben werden, die beispielsweise mit IN kombiniert werden kann, um Zeilen abzugleichen. Die WriteOnlyCollection hilft uns hier immer noch, da wir die Methode WriteOnlyCollection.select() verwenden, um diese SELECT-Anweisung für uns zu generieren und die Methode Select.with_only_columns() zu verwenden, um eine Skalare Unterabfrage zu erzeugen.

>>> from sqlalchemy import update
>>> subq = bank_audit.account_transactions.select().with_only_columns(AccountTransaction.id)
>>> session.execute(
...     update(AccountTransaction)
...     .values(description=AccountTransaction.description + " (audited)")
...     .where(AccountTransaction.id.in_(subq))
... )
UPDATE account_transaction SET description=(account_transaction.description || ?) WHERE account_transaction.id IN (SELECT account_transaction.id FROM audit_transaction WHERE ? = audit_transaction.audit_id AND account_transaction.id = audit_transaction.transaction_id) RETURNING id [...] (' (audited)', 1) <...>

Schreibgeschützte Sammlungen - API-Dokumentation

Objektname Beschreibung

WriteOnlyCollection

Schreibgeschützte Sammlung, die Änderungen in das Attribut-Ereignissystem synchronisieren kann.

WriteOnlyMapped

Stellt den ORM-abgebildeten Attributtyp für eine "schreibgeschützte" Beziehung dar.

class sqlalchemy.orm.WriteOnlyCollection

Schreibgeschützte Sammlung, die Änderungen in das Attribut-Ereignissystem synchronisieren kann.

Die WriteOnlyCollection wird in einer Abbildung verwendet, indem die Lazy-Loading-Strategie "write_only" mit relationship() verwendet wird. Für Hintergründe zu dieser Konfiguration siehe Schreibgeschützte Beziehungen.

Neu in Version 2.0.

Klassensignatur

class sqlalchemy.orm.WriteOnlyCollection (sqlalchemy.orm.writeonly.AbstractCollectionWriter)

method sqlalchemy.orm.WriteOnlyCollection.add(item: _T) None

Fügt ein Element zu dieser WriteOnlyCollection hinzu.

Das angegebene Element wird beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

method sqlalchemy.orm.WriteOnlyCollection.add_all(iterator: Iterable[_T]) None

Fügt ein iterierbares Objekt von Elementen zu dieser WriteOnlyCollection hinzu.

Die angegebenen Elemente werden beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

method sqlalchemy.orm.WriteOnlyCollection.delete() Delete

Generiert eine Delete-Anweisung, die sich auf Zeilen in Bezug auf diese Instanz-lokale WriteOnlyCollection bezieht.

method sqlalchemy.orm.WriteOnlyCollection.insert() Insert

Generiert für One-to-Many-Sammlungen ein Insert-Konstrukt, das neue Zeilen in Bezug auf diese Instanz-lokale WriteOnlyCollection einfügt.

Dieses Konstrukt wird nur für eine Relationship unterstützt, die den Parameter relationship.secondary nicht enthält. Für Beziehungen, die auf eine Many-to-Many-Tabelle verweisen, verwenden Sie normale Massen-INSERT-Techniken, um neue Objekte zu erzeugen, und verwenden Sie dann AbstractCollectionWriter.add_all(), um sie der Sammlung zuzuordnen.

method sqlalchemy.orm.WriteOnlyCollection.remove(item: _T) None

Entfernt ein Element aus dieser WriteOnlyCollection.

Das angegebene Element wird beim nächsten Flush aus der Sammlung der Elterninstanz entfernt.

method sqlalchemy.orm.WriteOnlyCollection.select() Select[Tuple[_T]]

Generiert ein Select-Konstrukt, das die Zeilen innerhalb dieser Instanz-lokalen WriteOnlyCollection repräsentiert.

method sqlalchemy.orm.WriteOnlyCollection.update() Update

Generiert eine Update-Anweisung, die sich auf Zeilen in Bezug auf diese Instanz-lokale WriteOnlyCollection bezieht.

class sqlalchemy.orm.WriteOnlyMapped

Stellt den ORM-abgebildeten Attributtyp für eine "schreibgeschützte" Beziehung dar.

Der Typ-Annotation WriteOnlyMapped kann in einer Annotated Declarative Table-Zuordnung verwendet werden, um anzuzeigen, dass die Lade-Strategie lazy="write_only" für ein bestimmtes relationship() verwendet werden soll.

Z. B.

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: WriteOnlyMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

Weitere Hintergrundinformationen finden Sie im Abschnitt Write Only Relationships.

Neu in Version 2.0.

Siehe auch

Write Only Relationships - Vollständiger Hintergrund

DynamicMapped - Enthält Legacy Query-Unterstützung

Klassensignatur

class sqlalchemy.orm.WriteOnlyMapped (sqlalchemy.orm.base._MappedAnnotationBase)

Dynamische Relationship-Loader

Legacy-Funktion

Die "dynamische" lazy Lade-Strategie ist die Legacy-Form dessen, was jetzt die "write_only" Strategie ist, die im Abschnitt Write Only Relationships beschrieben wird.

Die "dynamische" Strategie erzeugt ein Legacy- Query-Objekt aus der zugehörigen Sammlung. Ein wesentlicher Nachteil von "dynamischen" Beziehungen ist jedoch, dass es mehrere Fälle gibt, in denen die Sammlung vollständig durchlaufen wird, einige davon sind nicht offensichtlich. Dies kann nur durch sorgfältige Programmierung und Tests von Fall zu Fall verhindert werden. Daher sollte für die Verwaltung wirklich großer Sammlungen die WriteOnlyCollection bevorzugt werden.

Der dynamische Lader ist auch nicht mit der Asynchronous I/O (asyncio)-Erweiterung kompatibel. Er kann mit einigen Einschränkungen verwendet werden, wie in Asyncio dynamic guidelines angegeben, aber auch hier sollte die WriteOnlyCollection, die vollständig mit asyncio kompatibel ist, bevorzugt werden.

Die dynamische Relationship-Strategie ermöglicht die Konfiguration eines relationship(), das beim Zugriff auf eine Instanz ein Legacy- Query-Objekt anstelle der Sammlung zurückgibt. Die Query kann dann weiter modifiziert werden, sodass die Datenbanksammlung basierend auf Filterkriterien durchlaufen werden kann. Das zurückgegebene Query-Objekt ist eine Instanz von AppenderQuery, die das Lade- und Iterationsverhalten von Query mit rudimentären Sammlungsmutationsmethoden wie AppenderQuery.append() und AppenderQuery.remove() kombiniert.

Die "dynamische" Lade-Strategie kann mit der Typ-Annotation-Deklarativform unter Verwendung der DynamicMapped-Annotation-Klasse konfiguriert werden.

from sqlalchemy.orm import DynamicMapped


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    posts: DynamicMapped[Post] = relationship()

Oben gibt die User.posts-Sammlung für ein einzelnes User-Objekt das AppenderQuery-Objekt zurück, das eine Unterklasse von Query ist und auch grundlegende Sammlungsmutationsoperationen unterstützt.

jack = session.get(User, id)

# filter Jack's blog posts
posts = jack.posts.filter(Post.headline == "this is a post")

# apply array slices
posts = jack.posts[5:20]

Die dynamische Beziehung unterstützt begrenzte Schreiboperationen über die Methoden AppenderQuery.append() und AppenderQuery.remove().

oldpost = jack.posts.filter(Post.headline == "old post").one()
jack.posts.remove(oldpost)

jack.posts.append(Post("new post"))

Da die Lese-Seite der dynamischen Beziehung immer die Datenbank abfragt, sind Änderungen an der zugrunde liegenden Sammlung erst sichtbar, nachdem die Daten geleert wurden. Solange jedoch "autoflush" auf der verwendeten Session aktiviert ist, geschieht dies automatisch jedes Mal, wenn die Sammlung kurz vor einer Abfrage steht.

Dynamische Relationship-Loader - API

Objektname Beschreibung

AppenderQuery

Eine dynamische Abfrage, die grundlegende Sammlungsspeicheroperationen unterstützt.

DynamicMapped

Stellt den ORM-gemappten Attributtyp für eine "dynamische" Beziehung dar.

class sqlalchemy.orm.AppenderQuery

Eine dynamische Abfrage, die grundlegende Sammlungsspeicheroperationen unterstützt.

Methoden auf AppenderQuery umfassen alle Methoden von Query sowie zusätzliche Methoden für die Sammlungspersistenz.

Klassensignatur

class sqlalchemy.orm.AppenderQuery (sqlalchemy.orm.dynamic.AppenderMixin, sqlalchemy.orm.Query)

method sqlalchemy.orm.AppenderQuery.add(item: _T) None

geerbt von der AppenderMixin.add() Methode von AppenderMixin

Fügt ein Element zu dieser AppenderQuery hinzu.

Das angegebene Element wird beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

Diese Methode wird bereitgestellt, um die Vorwärtskompatibilität mit der WriteOnlyCollection-Sammlungsklasse zu unterstützen.

Neu in Version 2.0.

method sqlalchemy.orm.AppenderQuery.add_all(iterator: Iterable[_T]) None

geerbt von der AppenderMixin.add_all() Methode von AppenderMixin

Fügt ein Iterable von Elementen zu dieser AppenderQuery hinzu.

Die angegebenen Elemente werden beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

Diese Methode wird bereitgestellt, um die Vorwärtskompatibilität mit der WriteOnlyCollection-Sammlungsklasse zu unterstützen.

Neu in Version 2.0.

method sqlalchemy.orm.AppenderQuery.append(item: _T) None

geerbt von der AppenderMixin.append() Methode von AppenderMixin

Hängt ein Element an diese AppenderQuery an.

Das angegebene Element wird beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

method sqlalchemy.orm.AppenderQuery.count() int

geerbt von der AppenderMixin.count() Methode von AppenderMixin

Gibt eine Zählung der Zeilen zurück, die diese von dieser Query gebildete SQL-Abfrage zurückgeben würde.

Dies generiert die SQL für diese Abfrage wie folgt

SELECT count(1) AS count_1 FROM (
    SELECT <rest of query follows...>
) AS anon_1

Die obige SQL gibt eine einzelne Zeile zurück, die den aggregierten Wert der Zählfunktion darstellt; die Methode Query.count() gibt dann diesen einzelnen Integer-Wert zurück.

Warnung

Es ist wichtig zu beachten, dass der von count() zurückgegebene Wert **nicht dasselbe ist wie die Anzahl der ORM-Objekte, die diese Abfrage über eine Methode wie .all() zurückgeben würde**. Das Query-Objekt wird bei der Anforderung vollständiger Entitäten **Einträge basierend auf dem Primärschlüssel deduplizieren**, was bedeutet, dass, wenn derselbe Primärschlüsselwert mehr als einmal in den Ergebnissen vorkommt, nur ein Objekt dieses Primärschlüssels vorhanden sein wird. Dies gilt nicht für eine Abfrage, die sich auf einzelne Spalten bezieht.

Für eine feingranulare Kontrolle über bestimmte zu zählende Spalten, um die Verwendung einer Unterabfrage zu überspringen oder die FROM-Klausel anderweitig zu steuern, oder um andere Aggregatfunktionen zu verwenden, verwenden Sie expression.func-Ausdrücke in Verbindung mit Session.query(), z. B.:

from sqlalchemy import func

# count User records, without
# using a subquery.
session.query(func.count(User.id))

# return count of user "id" grouped
# by "name"
session.query(func.count(User.id)).group_by(User.name)

from sqlalchemy import distinct

# count distinct "name" values
session.query(func.count(distinct(User.name)))
method sqlalchemy.orm.AppenderQuery.extend(iterator: Iterable[_T]) None

geerbt von der AppenderMixin.extend() Methode von AppenderMixin

Fügt ein Iterable von Elementen zu dieser AppenderQuery hinzu.

Die angegebenen Elemente werden beim nächsten Flush in die Datenbank in Bezug auf die Sammlung der Elterninstanz persistiert.

method sqlalchemy.orm.AppenderQuery.remove(item: _T) None

geerbt von der AppenderMixin.remove() Methode von AppenderMixin

Entfernt ein Element aus dieser AppenderQuery.

Das angegebene Element wird beim nächsten Flush aus der Sammlung der Elterninstanz entfernt.

class sqlalchemy.orm.DynamicMapped

Stellt den ORM-gemappten Attributtyp für eine "dynamische" Beziehung dar.

Die Typ-Annotation DynamicMapped kann in einer Annotated Declarative Table-Zuordnung verwendet werden, um anzuzeigen, dass die Lade-Strategie lazy="dynamic" für ein bestimmtes relationship() verwendet werden soll.

Legacy-Funktion

Die "dynamische" lazy Lade-Strategie ist die Legacy-Form dessen, was jetzt die "write_only" Strategie ist, die im Abschnitt Write Only Relationships beschrieben wird.

Z. B.

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: DynamicMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

Weitere Hintergrundinformationen finden Sie im Abschnitt Dynamic Relationship Loaders.

Neu in Version 2.0.

Siehe auch

Dynamic Relationship Loaders - Vollständiger Hintergrund

WriteOnlyMapped - Vollständig 2.0-kompatible Version

Klassensignatur

class sqlalchemy.orm.DynamicMapped (sqlalchemy.orm.base._MappedAnnotationBase)

Festlegen von RaiseLoad

Eine "raise"-geladene Beziehung löst eine InvalidRequestError aus, wenn das Attribut normalerweise einen Lazy Load auslösen würde.

class MyClass(Base):
    __tablename__ = "some_table"

    # ...

    children: Mapped[List[MyRelatedClass]] = relationship(lazy="raise")

Oben löst der Attributzugriff auf die children-Sammlung eine Ausnahme aus, wenn sie nicht zuvor gefüllt wurde. Dies schließt Lesezugriffe ein, wirkt sich aber bei Sammlungen auch auf Schreibzugriffe aus, da Sammlungen nicht geändert werden können, ohne sie vorher zu laden. Der Grund dafür ist, sicherzustellen, dass eine Anwendung in einem bestimmten Kontext keine unerwarteten Lazy Loads ausgibt. Anstatt SQL-Logs durchlesen zu müssen, um festzustellen, dass alle notwendigen Attribute eager geladen wurden, löst die "raise"-Strategie sofort eine Ausnahme aus, wenn auf nicht geladene Attribute zugegriffen wird. Die "raise"-Strategie ist auch auf Abfrage-Optionsebene über die raiseload()-Loader-Option verfügbar.

Verwendung von Passive Deletes

Ein wichtiger Aspekt der Sammlungsverwaltung in SQLAlchemy ist, dass beim Löschen eines Objekts, das auf eine Sammlung verweist, SQLAlchemy die Objekte berücksichtigen muss, die sich in dieser Sammlung befinden. Diese Objekte müssen vom Elternteil getrennt werden, was für eine One-to-Many-Sammlung bedeutet, dass Fremdschlüsselspalten auf NULL gesetzt werden, oder basierend auf cascade-Einstellungen, stattdessen ein DELETE für diese Zeilen auslösen möchte.

Der Unit of Work-Prozess berücksichtigt Objekte nur zeilenweise, was bedeutet, dass eine DELETE-Operation impliziert, dass alle Zeilen innerhalb einer Sammlung während des Flush-Prozesses vollständig in den Speicher geladen werden müssen. Dies ist für große Sammlungen nicht praktikabel, daher versuchen wir stattdessen, uns auf die Fähigkeit der Datenbank zu verlassen, die Zeilen automatisch mithilfe von ON DELETE-Regeln für Fremdschlüssel zu aktualisieren oder zu löschen, und weisen das Unit of Work an, das Laden dieser Zeilen zur Handhabung zu vermeiden. Das Unit of Work kann angewiesen werden, auf diese Weise zu arbeiten, indem relationship.passive_deletes auf dem relationship()-Konstrukt konfiguriert wird; die verwendeten Fremdschlüsselbeschränkungen müssen ebenfalls korrekt konfiguriert sein.

Weitere Details zur vollständigen Konfiguration von "passive delete" finden Sie im Abschnitt Using foreign key ON DELETE cascade with ORM relationships.