过程中保存在 SQLAlchemy 会话中的对象
Objects saved in the SQLAlchemy session during process
我们的 Flask 网络应用程序中有一些功能,这些功能由一个函数调用组成,该函数调用调用许多子函数并在幕后做很多事情。例如,它向 (MSSQL) 数据库添加(金融)事务,将内容写入数据库中的 log-table 并更改特定对象的属性,从而导致特定 table 中的列发生更改我们的数据库。所有这些都是通过对象使用 SQLAlchemy 完成的。
在一种新方法中,由于可测试性,并且因为我们有时只想 显示 这些更改而不实际将它们提交到数据库,我们有这些功能 return 一个包含所有已更改对象的复合 Python 对象。
因此,我们没有在 inside 函数和子函数中提交数据库更改,而是 return 更改的对象,因此我们可以决定在主函数之外显示或保存它们功能。
所以主函数 return 是一个包含所有这些更改对象的复合对象,在主函数之外,我们将这些更改的对象添加到我们的 SQLAlchemy 会话中,并将会话提交到数据库。 (或者,如果我们只需要显示信息,我们不添加和提交)。我们这样做的方法是复合结果对象有一个 save_to_session()
函数,它使用 SQLAlchemy 的 bulk_save_objects()
操作保存我们更改的对象:
if result:
result.save_to_session(current_app.db_session)
current_app.db_session.commit()
def save_to_session(self, session):
session.bulk_save_objects(self.adminlog)
...
这种新方法导致了我们在 current_app.db_session.commit()
行中没有预料到的错误。似乎在过程结束时,当我们将 returned 对象添加到会话中并尝试将会话提交到数据库时,出现了关于 duplicate key[=44] 的错误=].
看起来在这个过程中,returned 对象已经添加到某个地方的会话中,并且 SQLAlchemy 尝试添加它们两次。
我们之所以得出这个结论,是因为当我们注释掉bulk_save_objects()
这个调用的时候,已经没有报错信息了。但是,更改的数据正确地提交到数据库,并且恰好一次。
当我们在发生此错误后检查数据库时,没有记录 包含错误消息中提到的主键。这是因为发生错误的回滚。所以这也不是数据库中已经存在记录,而是会话尝试两次添加相同的记录。
这是我们得到的错误,使用 pymssql 作为驱动程序:
sqlalchemy.exc.IntegrityError: (pymssql.IntegrityError) (2627,
b"Violation of PRIMARY KEY constraint 'PK_adminlog_id'.
Cannot insert duplicate key in object 'dbo.adminlog'.
The duplicate key value is (0E5537FF-E45C-40C5-98FC-7B1ACAD8104E).
DB-Lib error message 20018, severity 14:\n
General SQL Server error: Check messages from the SQL Server\n
")
[SQL:
'INSERT INTO adminlog (
alog_id,
alog_ppl_id,
alog_user_ppl_id,
alog_user_name,
alog_datetime,
[alog_ipAddress],
[alog_macAddress],
alog_comment,
alog_type,
alog_act_id,
alog_comp_id,
alog_artc_id)
VALUES (
%(alog_id)s,
%(alog_ppl_id)s,
%(alog_user_ppl_id)s,
%(alog_user_name)s,
%(alog_datetime)s,
%(alog_ipAddress)s,
%(alog_macAddress)s,
%(alog_comment)s,
%(alog_type)s,
%(alog_act_id)s,
%(alog_comp_id)s,
%(alog_artc_id)s)']
[parameters: (
{'alog_act_id': None,
'alog_comment': 'Le service a été ajouté. Cours Coll (119,88)',
'alog_datetime': datetime.datetime(2018, 10, 29, 13, 46, 54, 837178),
'alog_macAddress': b'4A-NO-NY-MO-US',
'alog_type': b'user',
'alog_artc_id': None,
'alog_comp_id': None,
'alog_id': b'0E5537FF-E45C-40C5-98FC-7B1ACAD8104E',
'alog_user_ppl_id': b'99999999-9999-9999-1111-999999999999',
'alog_user_name': 'System',
'alog_ipAddress': b'0.0.0.0',
'alog_ppl_id': b'AE841D1C-5D8D-47F7-B81F-89C5C931BD14'},
{'alog_act_id': None,
'alog_comment': 'Le service a été supprimé.
01/12/2019 Cours Coll (119,88)',
'alog_datetime': datetime.datetime(2018, 10, 29, 13, 46, 55, 71600),
'alog_macAddress': b'4A-NO-NY-MO-US',
'alog_type': b'user',
'alog_artc_id': None,
'alog_comp_id': None,
'alog_id': b'E22176FB-7490-470F-A8BA-A35D5F55A96A',
'alog_user_ppl_id': b'99999999-9999-9999-1111-999999999999',
'alog_user_name': 'System',
'alog_ipAddress': b'0.0.0.0',
'alog_ppl_id': b'AE841D1C-5D8D-47F7-B81F-89C5C931BD14'}
)]
我们在使用 PyODBC 时遇到了类似的错误:
sqlalchemy.exc.IntegrityError: (pyodbc.IntegrityError) ('23000',
"[23000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Violation of PRIMARY KEY constraint 'PK_adminlog_id'.
Cannot insert duplicate key in object 'dbo.adminlog'.
The duplicate key value is (F5CABD8F-E000-4677-8F5F-78B4CD3B9560). (2627) (SQLExecDirectW);
[23000] [Microsoft][SQL Server Native Client 11.0][SQL Server]The statement has been terminated. (3621)")
[SQL: 'INSERT INTO adminlog (
alog_id,
alog_ppl_id,
alog_user_ppl_id,
alog_user_name,
alog_datetime,
[alog_ipAddress],
[alog_macAddress],
alog_comment,
alog_type,
alog_act_id,
alog_comp_id,
alog_artc_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)']
[parameters: ((
b'F5CABD8F-E000-4677-8F5F-78B4CD3B9560',
b'0D10D3EF-F37E-45BE-8EED-B5987AE80732',
b'99999999-9999-9999-1111-999999999999',
'System',
datetime.datetime(2018, 10, 29, 13, 51, 30, 555495),
b'0.0.0.0',
b'4A-NO-NY-MO-US',
'Le service a été ajouté. Cours Coll (119,88)',
b'user',
None,
None,
None),
(
b'39395ACA-0AFB-4C5F-90D4-0C6F95D7B8BC',
b'0D10D3EF-F37E-45BE-8EED-B5987AE80732',
b'99999999-9999-9999-1111-999999999999',
'System',
datetime.datetime(2018, 10, 29, 13, 51, 30, 777909),
b'0.0.0.0',
b'4A-NO-NY-MO-US',
'Le service a été supprimé. 01/12/2019 Cours Coll (119,88)',
b'user',
None,
None,
None)
)]
我的问题是,是否有一个自动过程可以在我们不使用 session.add()
的情况下将(更改的)对象添加到会话中?
SQLAlchemy 中是否有一个选项可以禁用此行为,并且仅在使用 session.add(object)
明确完成时才提交会话?
My question is, is there an automatic process that adds (changed) objects to the session, without us using session.add()
?
至少有一项功能可以将对象拉到 Session
而无需显式添加它们:save-update
cascade。当一个对象被添加到 Session
时,所有通过配置了此级联的 relationship()
属性与之关联的对象也将被放置在 Session
中。当一个对象与另一个已经在 Session
.
中的对象相关联时,也会发生同样的情况
Is there an option in SQLAlchemy to disable this behaviour and only commit to the session when it's explicitly done using session.add(object)
?
您当然可以将 relationship()
属性配置为不包含此行为,但似乎没有全局开关可以完全禁用级联。
如果您的代码中存在这种情况,那么对象被添加两次的原因是您已经明确地这样做了。 bulk operations 省略了 Session
的大多数更高级的功能以支持原始性能——例如,如果一个对象已经被持久化,它们就不会与 Session
协调,它们也不会附加将对象持久化到 Session
:
The objects as given have no defined relationship to the target Session
, even when the operation is complete, meaning there’s no overhead in attaching them or managing their state in terms of the identity map or session.
至于问题首先出现的原因,您不需要手动为对象保留一个 "staging area" – 您的复合对象。这正是 Session
与正确使用事务相结合的目的。函数和子函数应该在有意义的时候向 Session
添加对象,但它们 不应该控制正在进行的事务 。这应该只发生在你的主要功能之外,你现在正在处理你的复合对象。如果回滚,所有更改都会消失。
在测试中,您可以绕过 a Session
that has joined an external transaction,无论被测代码做什么,都将显式回滚。
我们的 Flask 网络应用程序中有一些功能,这些功能由一个函数调用组成,该函数调用调用许多子函数并在幕后做很多事情。例如,它向 (MSSQL) 数据库添加(金融)事务,将内容写入数据库中的 log-table 并更改特定对象的属性,从而导致特定 table 中的列发生更改我们的数据库。所有这些都是通过对象使用 SQLAlchemy 完成的。
在一种新方法中,由于可测试性,并且因为我们有时只想 显示 这些更改而不实际将它们提交到数据库,我们有这些功能 return 一个包含所有已更改对象的复合 Python 对象。 因此,我们没有在 inside 函数和子函数中提交数据库更改,而是 return 更改的对象,因此我们可以决定在主函数之外显示或保存它们功能。
所以主函数 return 是一个包含所有这些更改对象的复合对象,在主函数之外,我们将这些更改的对象添加到我们的 SQLAlchemy 会话中,并将会话提交到数据库。 (或者,如果我们只需要显示信息,我们不添加和提交)。我们这样做的方法是复合结果对象有一个 save_to_session()
函数,它使用 SQLAlchemy 的 bulk_save_objects()
操作保存我们更改的对象:
if result:
result.save_to_session(current_app.db_session)
current_app.db_session.commit()
def save_to_session(self, session):
session.bulk_save_objects(self.adminlog)
...
这种新方法导致了我们在 current_app.db_session.commit()
行中没有预料到的错误。似乎在过程结束时,当我们将 returned 对象添加到会话中并尝试将会话提交到数据库时,出现了关于 duplicate key[=44] 的错误=].
看起来在这个过程中,returned 对象已经添加到某个地方的会话中,并且 SQLAlchemy 尝试添加它们两次。
我们之所以得出这个结论,是因为当我们注释掉bulk_save_objects()
这个调用的时候,已经没有报错信息了。但是,更改的数据正确地提交到数据库,并且恰好一次。
当我们在发生此错误后检查数据库时,没有记录 包含错误消息中提到的主键。这是因为发生错误的回滚。所以这也不是数据库中已经存在记录,而是会话尝试两次添加相同的记录。
这是我们得到的错误,使用 pymssql 作为驱动程序:
sqlalchemy.exc.IntegrityError: (pymssql.IntegrityError) (2627,
b"Violation of PRIMARY KEY constraint 'PK_adminlog_id'.
Cannot insert duplicate key in object 'dbo.adminlog'.
The duplicate key value is (0E5537FF-E45C-40C5-98FC-7B1ACAD8104E).
DB-Lib error message 20018, severity 14:\n
General SQL Server error: Check messages from the SQL Server\n
")
[SQL:
'INSERT INTO adminlog (
alog_id,
alog_ppl_id,
alog_user_ppl_id,
alog_user_name,
alog_datetime,
[alog_ipAddress],
[alog_macAddress],
alog_comment,
alog_type,
alog_act_id,
alog_comp_id,
alog_artc_id)
VALUES (
%(alog_id)s,
%(alog_ppl_id)s,
%(alog_user_ppl_id)s,
%(alog_user_name)s,
%(alog_datetime)s,
%(alog_ipAddress)s,
%(alog_macAddress)s,
%(alog_comment)s,
%(alog_type)s,
%(alog_act_id)s,
%(alog_comp_id)s,
%(alog_artc_id)s)']
[parameters: (
{'alog_act_id': None,
'alog_comment': 'Le service a été ajouté. Cours Coll (119,88)',
'alog_datetime': datetime.datetime(2018, 10, 29, 13, 46, 54, 837178),
'alog_macAddress': b'4A-NO-NY-MO-US',
'alog_type': b'user',
'alog_artc_id': None,
'alog_comp_id': None,
'alog_id': b'0E5537FF-E45C-40C5-98FC-7B1ACAD8104E',
'alog_user_ppl_id': b'99999999-9999-9999-1111-999999999999',
'alog_user_name': 'System',
'alog_ipAddress': b'0.0.0.0',
'alog_ppl_id': b'AE841D1C-5D8D-47F7-B81F-89C5C931BD14'},
{'alog_act_id': None,
'alog_comment': 'Le service a été supprimé.
01/12/2019 Cours Coll (119,88)',
'alog_datetime': datetime.datetime(2018, 10, 29, 13, 46, 55, 71600),
'alog_macAddress': b'4A-NO-NY-MO-US',
'alog_type': b'user',
'alog_artc_id': None,
'alog_comp_id': None,
'alog_id': b'E22176FB-7490-470F-A8BA-A35D5F55A96A',
'alog_user_ppl_id': b'99999999-9999-9999-1111-999999999999',
'alog_user_name': 'System',
'alog_ipAddress': b'0.0.0.0',
'alog_ppl_id': b'AE841D1C-5D8D-47F7-B81F-89C5C931BD14'}
)]
我们在使用 PyODBC 时遇到了类似的错误:
sqlalchemy.exc.IntegrityError: (pyodbc.IntegrityError) ('23000',
"[23000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Violation of PRIMARY KEY constraint 'PK_adminlog_id'.
Cannot insert duplicate key in object 'dbo.adminlog'.
The duplicate key value is (F5CABD8F-E000-4677-8F5F-78B4CD3B9560). (2627) (SQLExecDirectW);
[23000] [Microsoft][SQL Server Native Client 11.0][SQL Server]The statement has been terminated. (3621)")
[SQL: 'INSERT INTO adminlog (
alog_id,
alog_ppl_id,
alog_user_ppl_id,
alog_user_name,
alog_datetime,
[alog_ipAddress],
[alog_macAddress],
alog_comment,
alog_type,
alog_act_id,
alog_comp_id,
alog_artc_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)']
[parameters: ((
b'F5CABD8F-E000-4677-8F5F-78B4CD3B9560',
b'0D10D3EF-F37E-45BE-8EED-B5987AE80732',
b'99999999-9999-9999-1111-999999999999',
'System',
datetime.datetime(2018, 10, 29, 13, 51, 30, 555495),
b'0.0.0.0',
b'4A-NO-NY-MO-US',
'Le service a été ajouté. Cours Coll (119,88)',
b'user',
None,
None,
None),
(
b'39395ACA-0AFB-4C5F-90D4-0C6F95D7B8BC',
b'0D10D3EF-F37E-45BE-8EED-B5987AE80732',
b'99999999-9999-9999-1111-999999999999',
'System',
datetime.datetime(2018, 10, 29, 13, 51, 30, 777909),
b'0.0.0.0',
b'4A-NO-NY-MO-US',
'Le service a été supprimé. 01/12/2019 Cours Coll (119,88)',
b'user',
None,
None,
None)
)]
我的问题是,是否有一个自动过程可以在我们不使用 session.add()
的情况下将(更改的)对象添加到会话中?
SQLAlchemy 中是否有一个选项可以禁用此行为,并且仅在使用 session.add(object)
明确完成时才提交会话?
My question is, is there an automatic process that adds (changed) objects to the session, without us using
session.add()
?
至少有一项功能可以将对象拉到 Session
而无需显式添加它们:save-update
cascade。当一个对象被添加到 Session
时,所有通过配置了此级联的 relationship()
属性与之关联的对象也将被放置在 Session
中。当一个对象与另一个已经在 Session
.
Is there an option in SQLAlchemy to disable this behaviour and only commit to the session when it's explicitly done using
session.add(object)
?
您当然可以将 relationship()
属性配置为不包含此行为,但似乎没有全局开关可以完全禁用级联。
如果您的代码中存在这种情况,那么对象被添加两次的原因是您已经明确地这样做了。 bulk operations 省略了 Session
的大多数更高级的功能以支持原始性能——例如,如果一个对象已经被持久化,它们就不会与 Session
协调,它们也不会附加将对象持久化到 Session
:
The objects as given have no defined relationship to the target
Session
, even when the operation is complete, meaning there’s no overhead in attaching them or managing their state in terms of the identity map or session.
至于问题首先出现的原因,您不需要手动为对象保留一个 "staging area" – 您的复合对象。这正是 Session
与正确使用事务相结合的目的。函数和子函数应该在有意义的时候向 Session
添加对象,但它们 不应该控制正在进行的事务 。这应该只发生在你的主要功能之外,你现在正在处理你的复合对象。如果回滚,所有更改都会消失。
在测试中,您可以绕过 a Session
that has joined an external transaction,无论被测代码做什么,都将显式回滚。