Что происходит, это добавляет Pet
объекты, прежде чем они
фактически добавлен в сеанс?
Вставка <Person: Johnny>
неявно вставляет <Pet: Packets>
и <Pet: Shiloh>
; вставка <Person: Steph>
неявно вставляет <Pet: Sally>
.
Это потому, что backref
создает двунаправленные отношения.
Как описано здесь в документах:
[...] когда ключевое слово backref
используется для одного отношения, оно
точно так же, как если бы [...] были созданы две связи
индивидуально используя back_populates
[...]
Вы создаете Pet
экземпляров, которые относятся к Person
экземплярам, которые еще не существуют в базе данных. При использовании настроек каскадирования по умолчанию это приводит к неявным вставкам связанных объектов для представления обоих направлений взаимосвязи.
Это можно наблюдать, создав двигатель с echo
, установленным на True
:
engine = create_engine(connection_string, echo=True)
Включает базовую мощность двигателя:
# Time stamps and log level omitted for brevity
# First iteration of the loop (Johnny):
sqlalchemy.engine.base.Engine INSERT INTO "Person" (name) VALUES (?)
sqlalchemy.engine.base.Engine ('Johnny',)
sqlalchemy.engine.base.Engine INSERT INTO "Pet" (name, person_id) VALUES (?, ?)
sqlalchemy.engine.base.Engine ('Packets', 1)
sqlalchemy.engine.base.Engine INSERT INTO "Pet" (name, person_id) VALUES (?, ?)
sqlalchemy.engine.base.Engine ('Shiloh', 1)
# Second iteration of the loop (Steph):
sqlalchemy.engine.base.Engine INSERT INTO "Person" (name) VALUES (?)
sqlalchemy.engine.base.Engine ('Steph',)
sqlalchemy.engine.base.Engine INSERT INTO "Pet" (name, person_id) VALUES (?, ?)
sqlalchemy.engine.base.Engine ('Sally', 2)
# Third to fifth iteration: the Pets already exist.
Обратный путь аналогичен; если вы сначала укажете список питомцев, ваш вывод будет выглядеть так:
Added: <Pet: Packets> # implicitly creates Person Johnny and, through Johnny, Pet Shiloh
Added: <Pet: Sally> # implicitly creates Person Steph
Already exists: <Pet: Shiloh>
Already exists: <Person: Johnny>
Already exists: <Person: Steph>
Как отметил Илья Эверила в комментариях, самый простой способ отключить неявную вставку Pets - удалить параметр save-update
из отношения cascades
:
pets = relationship("Pet", backref="person", cascade="merge")
Обратите внимание, что выдает предупреждение:
SAWarning: объект типа <Pet>
не находится в сеансе, добавьте операцию вместе
Person.pets
не будет продолжено
Более подробный способ предотвратить неявное создание домашних животных посредством отношений - отложить их инстанцирование до тех пор, пока люди не будут вставлены, например ::101061
# Don't instantiate just yet
# pets = [
# Pet(name="Packets", person=persons[0]),
# Pet(name="Sally", person=persons[1]),
# Pet(name="Shiloh", person=persons[0]),
# ]
pets = {persons[0]: ['Packets', 'Shiloh'],
persons[1]: ['Sally']}
for item in persons:
if session.query(item.__class__).filter_by(name=item.name).one_or_none():
print(f"Already exists: {item}")
continue
session.add(item)
session.commit()
print(f"Added: {item}")
for pet in pets[item]:
p = Pet(name=pet, person=item)
session.add(p)
session.commit()
print(f"Added: {p}")
Выход:
Added: <Person: Johnny>
Added: <Pet: Packets>
Added: <Pet: Shiloh>
Added: <Person: Steph>
Added: <Pet: Sally>
Однако, с поведением по умолчанию, вы можете фактически опустить явную вставку Pets. Итерация persons
также вставит все экземпляры Pet; три ненужных запроса пропущены.