SQLAlchemy 关系追加错误
我一直在用 SQLAlchemy 0.9.2 和 Python 2.7.3,遇到了一个奇怪的问题,搞不太清楚怎么回事。以下是我相关的代码:
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parents'
__table_args__ = (UniqueConstraint('first_name', 'last_name', name='_name_constraint'),)
id = Column(Integer, primary_key=True)
first_name = Column(String(32), nullable=False)
last_name = Column(String(32), nullable=False)
children = relationship(Child, cascade='all,delete', backref='parent')
## Constructors and other methods ##
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parents.id'))
foo = Column(String(32), nullable=False)
## Constructors and other methods ##
这是一些比较基础的模型。我的问题是,我想给一个已经保存在数据库里的父对象添加一个子对象,但这个子对象现在关联的是一个不在数据库里的父对象。举个例子:
database_engine = create_engine("mysql://user:password@localhost/db", echo=False)
session = scoped_session(sessionmaker(autoflush=True,autocommit=False))
p1 = Parent("Foo", "Bar") # Create a parent and append a child
c1 = Child("foo")
p1.children.append(c1)
session.add(p1)
session.commit() # This works without a problem
db_parent = session.query(Parent).first()
db_parent.children.append(Child("bar"))
session.commit() # This also works without a problem
p2 = Parent("Foo", "Bar")
c3 = Child("baz")
p2.children.append(c3)
db_parent = session.query(Parent).first()
db_parent.children.append(p2.children[0])
session.commit() # ERROR: This blows up
我收到的错误是破坏了一个完整性约束,具体是 '_name_constraint'。SQLAlchemy 告诉我,它试图插入一个信息相同的父对象。我的问题是,为什么它会试图添加一个额外的父对象呢?
到目前为止,我采取了以下步骤,但还是没有找到好的答案:
- 我检查了
db_parent.children[2]
,发现它和p1
指向的是同一个内存地址,当我把它添加到列表后。 - 我检查了
p2.children
,在添加后奇怪的是,p2
没有子对象,尽管我已经把它的子对象添加到了db_parent
。我觉得这和发生的事情有关系,但我不明白为什么会这样。
如果有人能帮忙,我会非常感激,因为我实在搞不懂这里发生了什么。如果你需要我提供更多信息,请告诉我。提前谢谢!
1 个回答
1
好的,经过一番深入研究,我觉得我找到了问题的解决办法,但我还不太明白为什么会这样。不过,我有个猜测。我发现的解决方案是在 session.commit()
之前使用 session.expunge(p2)
。
我开始研究 SQLAlchemy 的内部机制,特别是实例状态。我发现,一旦你把子对象添加到父对象,原始父对象的状态就会变成待处理状态。这里有个例子:
from sqlalchemy import inspect
p2 = Parent("Foo", "Bar")
p2_inst = inspect(p2)
c3 = Child("Baz")
c3_inst = inspect(c3)
db_parent = session.query(Parent).first()
db_parent_inst = inspect(db_parent)
print("Pending State before append:")
print("p2_inst : {}".format(p2_inst.pending))
print("c3_inst : {}".format(c3_inst.pending))
print("db_parent_inst : {}".format(db_parent_inst.pending))
db_parent.children.append(p2.children[0])
print("Pending State after append:")
print("p2_inst : {}".format(p2_inst.pending))
print("c3_inst : {}".format(c3_inst.pending))
print("db_parent_inst : {}".format(db_parent_inst.pending))
session.expunge(p2)
print("Pending State after expunge:")
print("p2_inst : {}".format(p2_inst.pending))
print("c3_inst : {}".format(c3_inst.pending))
print("db_parent_inst : {}".format(db_parent_inst.pending))
session.commit()
运行这个代码的结果是:
Pending State before append:
p2_inst : False
c3_inst : False
db_parent_inst : False
Pending State after append:
p2_inst : True
c3_inst : True
db_parent_inst : False
Pending State after expunge:
p2_inst : False
c3_inst : True
db_parent_inst : False
就这样。一开始我想了想,这似乎是有道理的。因为你并没有对 MySQL 中的记录做任何操作,所以没有理由让 db_parent 进入“待处理”状态。我猜 p2
变成待处理状态的原因可能是操作顺序的问题?为了让 c3
变成待处理状态,它的所有关系必须存在(包括 p2
),所以即使你改变了子对象的父对象,session 仍然认为它需要添加这个父对象。
如果有人对 SQLAlchemy 更了解,希望能纠正我,但就我所知,这就是我目前的最佳解释 :)