我正在编写一个python脚本,该脚本创建一些SQLAlchemy对象,检查哪些对象已添加到数据库中,然后添加任何新对象。我的脚本如下:
from sqlalchemy import Column, String, Integer, ForeignKey, create_engine
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# Define models
class Person(Base):
__tablename__ = "Person"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
pets = relationship("Pet", backref="person")
def __repr__(self):
return f"<Person: {self.name}>"
class Pet(Base):
__tablename__ = "Pet"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
person_id = Column(Integer, ForeignKey("Person.id"))
def __repr__(self):
return f"<Pet: {self.name}>"
connection_string = "sqlite:///db.sqlite3"
engine = create_engine(connection_string)
session = Session(
bind=engine, expire_on_commit=False, autoflush=False, autocommit=False
)
# Build tables
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# Create data
persons = [
Person(name="Johnny"),
Person(name="Steph"),
]
pets = [
Pet(name="Packets", person=persons[0]),
Pet(name="Sally", person=persons[1]),
Pet(name="Shiloh", person=persons[0]),
]
# Populate tables with data
for items in [persons, pets]:
for item in items:
q = session.query(item.__class__).filter_by(name=item.name).one_or_none()
if q:
print(f"Already exists: {item}")
continue
session.add(item)
session.commit()
print(f"Added: {item}")
运行它时,我得到以下结果:
Added: <Person: Johnny>
Added: <Person: Steph>
Already exists: <Pet: Packets>
Already exists: <Pet: Sally>
Already exists: <Pet: Shiloh>
我希望结果看起来像这样:
Added: <Person: Johnny>
Added: <Person: Steph>
Added: <Pet: Packets>
Added: <Pet: Sally>
Added: <Pet: Shiloh>
在将Pet
对象实际添加到会话中之前发生了什么情况?如何防止这种情况,使我的输出符合预期?
答案 0 :(得分:2)
正在发生的事情是在
Pet
对象被添加之前 确实添加到了会话中?
插入<Person: Johnny>
会隐式插入<Pet: Packets>
和<Pet: Shiloh>
;插入<Person: Steph>
会隐式插入<Pet: Sally>
。
这是因为backref
创建了双向关系。
如文档中的here所述:
[...]在单个关系上使用
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>
正如IljaEverilä在评论中指出的那样,禁用隐式插入Pets的最简单方法是从关系的save-update
中删除cascades
设置:
pets = relationship("Pet", backref="person", cascade="merge")
注意,发出警告:
SAWarning:
<Pet>
类型的对象不在会话中,请添加操作Person.pets
将不会继续
阻止宠物通过关系隐式创建的一种更冗长的方法是将其实例化推迟到插入人物之后,例如:
# 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>
但是,使用默认行为,您可以有效地省略Pet的显式插入。仅迭代persons
,也会插入所有Pet实例;跳过三个不必要的查询。