Ich möchte ein Objekt aus der Datenbank abrufen, wenn es bereits vorhanden ist (basierend auf den angegebenen Parametern), oder es erstellen, wenn dies nicht der Fall ist.
Djangos get_or_create
(oder Quelle ) tut dies. Gibt es eine entsprechende Abkürzung in SQLAlchemy?
Ich schreibe es gerade explizit so aus:
def get_or_create_instrument(session, serial_number):
instrument = session.query(Instrument).filter_by(serial_number=serial_number).first()
if instrument:
return instrument
else:
instrument = Instrument(serial_number)
session.add(instrument)
return instrument
python
django
sqlalchemy
FogleBird
quelle
quelle
session.merge
: stackoverflow.com/questions/12297156/…Antworten:
Das ist im Grunde der Weg, es gibt keine Abkürzung AFAIK.
Sie könnten es natürlich verallgemeinern:
quelle
try...except IntegrityError: instance = session.Query(...)
um densession.add
Block ein befinden.Nach der Lösung von @WoLpH ist dies der Code, der für mich funktioniert hat (einfache Version):
Damit kann ich jedes Objekt meines Modells abrufen oder erstellen.
Angenommen, mein Modellobjekt ist:
Um mein Objekt zu erhalten oder zu erstellen, schreibe ich:
quelle
commit
(oder zumindest nur einflush
stattdessen zu verwenden). Dies überlässt die Sitzungskontrolle dem Aufrufer dieser Methode und riskiert nicht, ein vorzeitiges Commit auszugeben. Auch die Verwendung vonone_or_none()
anstelle von istfirst()
möglicherweise etwas sicherer.Ich habe mit diesem Problem gespielt und eine ziemlich robuste Lösung gefunden:
Ich habe gerade einen ziemlich umfangreichen Blog-Beitrag über alle Details geschrieben, aber ein paar gute Ideen, warum ich das verwendet habe.
Es wird in ein Tupel entpackt, das Ihnen sagt, ob das Objekt vorhanden ist oder nicht. Dies kann in Ihrem Workflow häufig hilfreich sein.
Die Funktion bietet die Möglichkeit, mit
@classmethod
dekorierten Erstellerfunktionen (und spezifischen Attributen) zu arbeiten.Die Lösung schützt vor Race-Bedingungen, wenn mehr als ein Prozess mit dem Datenspeicher verbunden ist.
EDIT: Ich habe zu geändert
session.commit()
,session.flush()
wie in diesem Blog-Beitrag erklärt . Beachten Sie, dass diese Entscheidungen spezifisch für den verwendeten Datenspeicher sind (in diesem Fall Postgres).BEARBEITEN 2: Ich habe mit einem {} als Standardwert in der Funktion aktualisiert, da dies ein typisches Python-Gotcha ist. Danke für den Kommentar , Nigel! Wenn Sie neugierig auf dieses Problem sind, lesen Sie diese StackOverflow-Frage und diesen Blog-Beitrag .
quelle
get_or_create
ist nicht threadsicher. Es ist nicht atomar. Außerdem gibt Djangoget_or_create
ein True-Flag zurück, wenn die Instanz erstellt wurde, oder ein False-Flag, wenn dies nicht der Fall ist.get_or_create
es fast genau das Gleiche. Diese Lösung gibt auch dasTrue/False
Flag zurück, um zu signalisieren, ob das Objekt erstellt oder abgerufen wurde, und ist auch nicht atomar. Thread-Sicherheit und atomare Aktualisierungen sind jedoch ein Problem für die Datenbank, nicht für Django, Flask oder SQLAlchemy, und werden sowohl in dieser als auch in Djangos Lösung durch Transaktionen in der Datenbank gelöst.IntegrityError
Fall nicht zurückkehren,False
da dieser Client das Objekt nicht erstellt hat?Eine modifizierte Version von eriks ausgezeichneter Antwort
create_method
. Wenn das erstellte Objekt Beziehungen hat und über diese Beziehungen Mitglieder zugewiesen werden, wird es automatisch zur Sitzung hinzugefügt. Erstellen Sie z. B. einebook
, die eine entsprechende Beziehung hat,user_id
und fügen Sie sieuser
dannbook.user=<user object>
innerhalb der Sitzung hinzu. Dies bedeutet, dass es sich im Inneren befinden muss , um von einem eventuellen Rollback zu profitieren. Beachten Sie, dass automatisch ein Flush ausgelöst wird.create_method
book
create_method
with
begin_nested
Beachten Sie, dass MySQL , wenn verwenden, muss die Transaktionsisolationsstufe eingestellt werden ,
READ COMMITTED
anstattREPEATABLE READ
dafür zu arbeiten. Djangos get_or_create (und hier ) verwendet dieselbe Strategie, siehe auch die Django- Dokumentation .quelle
IntegrityError
erneute Abfrage schlägt jedoch möglicherweise immer nochNoResultFound
mit der MySQL-Standardisolationsstufe fehl,REPEATABLE READ
wenn die Sitzung das Modell zuvor in derselben Transaktion abgefragt hat. Die beste Lösung, die ich finden könnte, besteht darin,session.commit()
vor dieser Abfrage anzurufen , was ebenfalls nicht ideal ist, da der Benutzer dies möglicherweise nicht erwartet. Die Antwort, auf die verwiesen wird, hat dieses Problem nicht, da session.rollback () den gleichen Effekt hat wie das Starten einer neuen Transaktion.commit
das Funktionieren dieser Funktion möglicherweise schlechter ist als das Ausführen einer Funktionrollback
, obwohl dies für bestimmte Anwendungsfälle akzeptabel sein kann.commit()
. Wenn ich den Code richtig verstehe, ist es das, was Django tut., so it does not look like they try to handle this. Looking at the [source](https://github.com/django/django/blob/master/django/db/models/query.py#L491) confirms this. I'm not sure I understand your reply, you mean the user should put his/her query in a nested transaction? It's not clear to me how a
SAVEPOINT" zu verwendenREPEATABLE READ
. Wenn kein Effekt vorliegt, scheint die Situation nicht mehr zu retten. Wenn der Effekt vorliegt, kann die allerletzte Abfrage verschachtelt werden.READ COMMITED
, vielleicht sollte ich meine Entscheidung überdenken, die Datenbankstandards nicht zu berühren. Ich habe getestet, dass das Wiederherstellen einesSAVEPOINT
von vor einer Abfrage den Eindruck erweckt, dass diese Abfrage nie stattgefunden hatREPEATABLE READ
. Daher fand ich es notwendig, die Abfrage in der try-Klausel in eine verschachtelte Transaktion einzuschließen, damit die Abfrage in derIntegrityError
Except-Klausel überhaupt funktionieren kann.Dieses SQLALchemy-Rezept macht den Job schön und elegant.
Als Erstes müssen Sie eine Funktion definieren, die eine Sitzung erhält, mit der Sie arbeiten können, und der Sitzung () ein Wörterbuch zuordnen, das die aktuellen eindeutigen Schlüssel verfolgt.
Ein Beispiel für die Verwendung dieser Funktion wäre ein Mixin:
Und schließlich das einzigartige Modell get_or_create erstellen:
Das Rezept geht tiefer in die Idee ein und bietet verschiedene Ansätze, aber ich habe diesen mit großem Erfolg verwendet.
quelle
Das semantisch am nächsten ist wahrscheinlich:
Session
Ich bin mir nicht sicher, wie koscher es ist, sich auf eine global in sqlalchemy definierte Version zu verlassen , aber die Django-Version nimmt keine Verbindung auf, also ...Das zurückgegebene Tupel enthält die Instanz und einen Booleschen Wert, der angibt, ob die Instanz erstellt wurde (dh es ist falsch, wenn wir die Instanz aus der Datenbank lesen).
Django
get_or_create
wird oft verwendet, um sicherzustellen, dass globale Daten verfügbar sind, sodass ich mich zum frühestmöglichen Zeitpunkt verpflichte.quelle
scoped_session
Dies sollte eine thread-sichere Sitzungsverwaltung implementieren (gab es diese im Jahr 2014?).Ich habe @Kevin leicht vereinfacht. Lösung, um zu vermeiden, dass die gesamte Funktion in eine
if
/else
-Anweisung eingeschlossen wird. Auf diese Weise gibt es nur einereturn
, die ich sauberer finde:quelle
Abhängig von der von Ihnen gewählten Isolationsstufe würde keine der oben genannten Lösungen funktionieren. Die beste Lösung, die ich gefunden habe, ist ein RAW-SQL in der folgenden Form:
Dies ist unabhängig von der Isolationsstufe und dem Grad der Parallelität transaktionssicher.
Achtung: Um die Effizienz zu steigern, ist es ratsam, einen INDEX für die eindeutige Spalte zu haben.
quelle