SqlAlchemy:插入新行并修改另一行:"Can't attach instance <ObjectT>; another instance with key is already present in this session"
SqlAlchemy: Insert New Row and Modify Another: "Can't attach instance <ObjectT>; another instance with key is already present in this session"
我必须在提交时同时插入一行并修改另一行(设置其 ACTIVE=N
)。添加的新行是旧行的副本,具有新 ID。我使用 Python 的 copy.deepcopy
来保存旧对象,然后我擦除当前对象的 id
以使其符合插入新序列值的条件。这两个操作都应该在交易中发生。
# First fetch existing agreement. Initially "postApproveOldAgreement" is None.
postApproveOldAgreement = None
agreement = db_session.query(AgreementT).filter(AgreementT.id == form['someId']).first()
...
if someCondition:
postApproveOldAgreement = copy.deepcopy(agreement)
postApproveOldAgreement.active = 'N' # Modify old agreement
agreement.id = None # Make this one eligible for new insertion
# Continue filling out the "agreement" object
...
# Insert/Modify and Commit
db_session.add(agreement)
if postApproveOldAgreement != None:
db_session.add(postApproveOldAgreement)
db_session.commit()
错误:
sqlalchemy.exc.InvalidRequestError:
Can't attach instance <AgreementT at 0x188e8abedc0>;
another instance with key (<class 'ets.db.models.AgreementT'>, (20812,), None) is already present in this session.
看起来 2 个实例引用了同一个密钥(我认为是因为您使用的是 deepcopy
)。您可以使用 Agreement
.
的新实例来完成此技巧
agreement = db.session.query(Agreement).filter(...).first()
# if some_condition... let's say condition works
agreement_data = deepcopy(agreement.__dict__)
# we don't need id + _sa_instance_state db instance
del agreement_data['id']
del agreement_data['_sa_instance_state']
agreement2 = Agreement(**agreement_data)
agreement2.active = 'N'
session.add(agreement2)
# let's try to change an agreement field
agreement.active = 'new val'
session.commit()
日志:
2021-07-30 17:18:22,658 INFO sqlalchemy.engine.Engine UPDATE agreement SET active=%(active)s WHERE agreement.id = %(agreement_id)s
2021-07-30 17:18:22,658 INFO sqlalchemy.engine.Engine [generated in 0.00012s] {'active': 'new val', 'agreement_id': 1}
2021-07-30 17:18:22,659 INFO sqlalchemy.engine.Engine INSERT INTO agreement (....) VALUES (....) RETURNING agreement.id
....
2021-07-30 17:18:22,660 INFO sqlalchemy.engine.Engine COMMIT
我使用此处概述的以下方法实现了它,https://www.atemon.com/blog/clone-a-sqlalchemy-db-object-with-new-primary-key/
# Initially
agreement = db_session.query(AgreementT).filter(AgreementT.id == form['agreementIdManual']).first()
postApproveOldAgreement = None
if (someCondition):
# Clone the current Agreement and erase its ID to make it Insert-eligible:
# see https://www.atemon.com/blog/clone-a-sqlalchemy-db-object-with-new-primary-key/
# Use expunge/make_transient
db_session.expunge(agreement)
make_transient(agreement)
agreement.id = None
# Modify new cloned agreement as necessary...
agreement.active = 'Y' # etc.
# De-activate the previous Agreement by **re-fetching** it and
# modifying it as necessary
postApproveOldAgreement = db_session.query(AgreementT).filter(AgreementT.id == form['agreementIdManual']).first()
postApproveOldAgreement.active = 'N'
# Insert and commit
db_session.add(agreement)
# Special Case: If postApproveOldAgreement is available, commit it as well
if postApproveOldAgreement != None:
db_session.add(postApproveOldAgreement)
db_session.commit()
我必须在提交时同时插入一行并修改另一行(设置其 ACTIVE=N
)。添加的新行是旧行的副本,具有新 ID。我使用 Python 的 copy.deepcopy
来保存旧对象,然后我擦除当前对象的 id
以使其符合插入新序列值的条件。这两个操作都应该在交易中发生。
# First fetch existing agreement. Initially "postApproveOldAgreement" is None.
postApproveOldAgreement = None
agreement = db_session.query(AgreementT).filter(AgreementT.id == form['someId']).first()
...
if someCondition:
postApproveOldAgreement = copy.deepcopy(agreement)
postApproveOldAgreement.active = 'N' # Modify old agreement
agreement.id = None # Make this one eligible for new insertion
# Continue filling out the "agreement" object
...
# Insert/Modify and Commit
db_session.add(agreement)
if postApproveOldAgreement != None:
db_session.add(postApproveOldAgreement)
db_session.commit()
错误:
sqlalchemy.exc.InvalidRequestError:
Can't attach instance <AgreementT at 0x188e8abedc0>;
another instance with key (<class 'ets.db.models.AgreementT'>, (20812,), None) is already present in this session.
看起来 2 个实例引用了同一个密钥(我认为是因为您使用的是 deepcopy
)。您可以使用 Agreement
.
agreement = db.session.query(Agreement).filter(...).first()
# if some_condition... let's say condition works
agreement_data = deepcopy(agreement.__dict__)
# we don't need id + _sa_instance_state db instance
del agreement_data['id']
del agreement_data['_sa_instance_state']
agreement2 = Agreement(**agreement_data)
agreement2.active = 'N'
session.add(agreement2)
# let's try to change an agreement field
agreement.active = 'new val'
session.commit()
日志:
2021-07-30 17:18:22,658 INFO sqlalchemy.engine.Engine UPDATE agreement SET active=%(active)s WHERE agreement.id = %(agreement_id)s
2021-07-30 17:18:22,658 INFO sqlalchemy.engine.Engine [generated in 0.00012s] {'active': 'new val', 'agreement_id': 1}
2021-07-30 17:18:22,659 INFO sqlalchemy.engine.Engine INSERT INTO agreement (....) VALUES (....) RETURNING agreement.id
....
2021-07-30 17:18:22,660 INFO sqlalchemy.engine.Engine COMMIT
我使用此处概述的以下方法实现了它,https://www.atemon.com/blog/clone-a-sqlalchemy-db-object-with-new-primary-key/
# Initially
agreement = db_session.query(AgreementT).filter(AgreementT.id == form['agreementIdManual']).first()
postApproveOldAgreement = None
if (someCondition):
# Clone the current Agreement and erase its ID to make it Insert-eligible:
# see https://www.atemon.com/blog/clone-a-sqlalchemy-db-object-with-new-primary-key/
# Use expunge/make_transient
db_session.expunge(agreement)
make_transient(agreement)
agreement.id = None
# Modify new cloned agreement as necessary...
agreement.active = 'Y' # etc.
# De-activate the previous Agreement by **re-fetching** it and
# modifying it as necessary
postApproveOldAgreement = db_session.query(AgreementT).filter(AgreementT.id == form['agreementIdManual']).first()
postApproveOldAgreement.active = 'N'
# Insert and commit
db_session.add(agreement)
# Special Case: If postApproveOldAgreement is available, commit it as well
if postApproveOldAgreement != None:
db_session.add(postApproveOldAgreement)
db_session.commit()