Chcę pobrać obiekt z bazy danych, jeśli już istnieje (na podstawie podanych parametrów) lub utworzyć go, jeśli nie.
Django get_or_create
(lub źródło ) robi to. Czy istnieje równoważny skrót w SQLAlchemy?
Obecnie piszę to wyraźnie w ten sposób:
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
źródło
źródło
session.merge
: stackoverflow.com/questions/12297156/…Odpowiedzi:
W zasadzie tak to zrobić, nie ma łatwo dostępnego skrótu AFAIK.
Możesz to oczywiście uogólnić:
źródło
try...except IntegrityError: instance = session.Query(...)
wokółsession.add
bloku powinien znajdować się znak .Po rozwiązaniu @WoLpH, oto kod, który działał dla mnie (wersja prosta):
Dzięki temu mogę get_or_create dowolny obiekt mojego modelu.
Załóżmy, że mój obiekt modelu to:
Aby pobrać lub stworzyć mój obiekt, piszę:
źródło
commit
(lub przynajmniej używać tylkoflush
a). Pozostawia to kontrolę sesji wywołującemu tę metodę i nie ryzykuje przedwczesnego zatwierdzenia. Ponadto użycieone_or_none()
zamiastfirst()
może być nieco bezpieczniejsze.Bawiłem się tym problemem i otrzymałem dość solidne rozwiązanie:
Właśnie napisałem dość obszerny wpis na blogu ze wszystkimi szczegółami, ale kilka pomysłów, dlaczego go użyłem.
Rozpakowuje się do krotki, która mówi, czy obiekt istniał, czy nie. Często może to być przydatne w Twoim przepływie pracy.
Funkcja daje możliwość pracy z
@classmethod
dekorowanymi funkcjami kreatora (i specyficznymi dla nich atrybutami).Rozwiązanie chroni przed warunkami wyścigu, gdy do magazynu danych jest podłączony więcej niż jeden proces.
EDYCJA: Zmieniłem
session.commit()
na,session.flush()
jak wyjaśniono w tym poście na blogu . Należy zauważyć, że te decyzje są specyficzne dla używanego magazynu danych (w tym przypadku Postgres).EDYCJA 2: Zaktualizowałem za pomocą {} jako wartości domyślnej w funkcji, ponieważ jest to typowa gotcha Pythona. Dzięki za komentarz , Nigel! Jeśli ciekawi Cię ten problem, sprawdź to pytanie StackOverflow i ten post na blogu .
źródło
get_or_create
jest bezpieczne dla wątków. To nie jest atomowe. Ponadto Django zwraca flagę True, jeśli instancja została utworzona lub flagę False w przeciwnym razie.get_or_create
get_or_create
robi to prawie to samo. To rozwiązanie również zwracaTrue/False
flagę sygnalizującą, czy obiekt został utworzony lub pobrany, a także nie jest atomowy. Jednak bezpieczeństwo wątków i aktualizacje atomowe są problemem dla bazy danych, a nie dla Django, Flask lub SQLAlchemy, a zarówno w tym rozwiązaniu, jak i w Django są rozwiązywane przez transakcje w bazie danych.IntegrityError
sprawa nie powinna powrócić,False
ponieważ ten klient nie utworzył obiektu?Zmodyfikowana wersja doskonałej odpowiedzi Erika
create_method
. Jeśli utworzony obiekt ma relacje i przypisane są do niego członkowie za pośrednictwem tych relacji, jest on automatycznie dodawany do sesji. Np. Utwórzbook
, który mauser_id
iuser
jako odpowiedni związek, a następnie wykonującbook.user=<user object>
wewnątrzcreate_method
, dodaszbook
do sesji. Oznacza to, żecreate_method
musi znajdować się w środku,with
aby skorzystać z ewentualnego wycofania. Zauważ, żebegin_nested
automatycznie uruchamia kolor.Zauważ, że jeśli używasz MySQL, poziom izolacji transakcji musi być ustawiony na,
READ COMMITTED
a nie,REPEATABLE READ
aby to zadziałało. Django get_or_create (i tutaj ) używa tego samego podstępu, zobacz także dokumentację Django .źródło
IntegrityError
ponowne zapytanie może nadal zakończyć się niepowodzeniem zNoResultFound
domyślnym poziomem izolacji MySQL,REPEATABLE READ
jeśli sesja wcześniej odpytała model w tej samej transakcji. Najlepszym rozwiązaniem, jakie mogłem wymyślić, jest zadzwonieniesession.commit()
przed tym zapytaniem, co również nie jest idealne, ponieważ użytkownik może się tego nie spodziewać. Przywoływana odpowiedź nie powoduje tego problemu, ponieważ session.rollback () ma taki sam efekt, jak rozpoczęcie nowej transakcji.commit
wnętrze tej funkcji jest prawdopodobnie gorsze niż wykonanie arollback
, mimo że w określonych przypadkach może być do zaakceptowania.commit()
sam. Jeśli moje zrozumienie kodu jest poprawne, to właśnie robi Django., 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
wpływów `READ COMMITTED SAVEPOINT`, z którymi czytaREPEATABLE READ
. Jeśli nie ma efektu, sytuacja wydaje się nie do rozwiązania, a jeśli skutek, to ostatnie zapytanie może zostać zagnieżdżone?READ COMMITED
, może powinienem przemyśleć swoją decyzję, aby nie zmieniać ustawień domyślnych bazy danych. Przetestowałem, że przywrócenieSAVEPOINT
zapytania sprzed wykonania zapytania sprawia, że to zapytanie nigdy się nie wydarzyłoREPEATABLE READ
. Dlatego stwierdziłem, że konieczne jest zawarcie zapytania w klauzuli try w zagnieżdżonej transakcji, aby zapytanie wIntegrityError
klauzuli except mogło w ogóle działać.Ten przepis SQLALchemy wykonuje zadanie ładnie i elegancko.
Pierwszą rzeczą do zrobienia jest zdefiniowanie funkcji, której dana jest sesja do pracy i powiązanie słownika z funkcją Session (), która śledzi bieżące unikalne klucze.
Przykładem wykorzystania tej funkcji może być mixin:
I wreszcie tworząc unikalny model get_or_create:
Przepis zagłębia się w pomysł i zapewnia różne podejścia, ale użyłem tego z wielkim sukcesem.
źródło
Najbliższe semantycznie jest prawdopodobnie:
nie jestem pewien, jak koszerne jest poleganie na globalnie zdefiniowanym
Session
w sqlalchemy, ale wersja Django nie ma połączenia, więc ...Zwrócona krotka zawiera instancję i wartość logiczną wskazującą, czy instancja została utworzona (tj. Jest fałszywa, jeśli odczytujemy instancję z bazy danych).
Django
get_or_create
jest często używane, aby upewnić się, że dane globalne są dostępne, więc zatwierdzam w możliwie najwcześniejszym momencie.źródło
scoped_session
, co powinno zaimplementować zarządzanie sesjami bezpieczne dla wątków (czy istniało to w 2014?).Trochę uprościłem @Kevin. rozwiązanie pozwalające uniknąć zawijania całej funkcji w instrukcji
if
/else
. W ten sposób jest tylko jedenreturn
, który uważam za czystszy:źródło
W zależności od przyjętego poziomu izolacji żadne z powyższych rozwiązań nie zadziała. Najlepsze rozwiązanie jakie znalazłem to RAW SQL w postaci:
Jest to bezpieczne transakcyjnie niezależnie od poziomu izolacji i stopnia równoległości.
Uwaga: aby było wydajne, dobrze byłoby mieć INDEKS dla unikalnej kolumny.
źródło