Python SQLAlchemy`Session.is_modified`返回`True`,即使对象列未被修改
我正在尝试检测中的对象何时被修改。从文档中: 检测对象上基于列的属性是否具有 更改,并因此生成UPDATE语句,请使用 对象会话(实例)。已修改(实例, include_collections=False) 但是,我看到Python SQLAlchemy`Session.is_modified`返回`True`,即使对象列未被修改,python,sqlalchemy,Python,Sqlalchemy,我正在尝试检测中的对象何时被修改。从文档中: 检测对象上基于列的属性是否具有 更改,并因此生成UPDATE语句,请使用 对象会话(实例)。已修改(实例, include_collections=False) 但是,我看到is_modified正在返回True,即使对象的列没有被修改,也没有UPDATE语句被发送到数据库。下面是一个工作示例和我正在使用的SQLAlchemy版本。你知道为什么会这样吗?我看了一眼会话的SQLAlchemy源代码。它被修改了,似乎使用了对象状态和历史记录 SQLAlc
is_modified
正在返回True
,即使对象的列没有被修改,也没有UPDATE
语句被发送到数据库。下面是一个工作示例和我正在使用的SQLAlchemy版本。你知道为什么会这样吗?我看了一眼会话的SQLAlchemy源代码。它被修改了,似乎使用了对象状态和历史记录
SQLAlchemy版本:
$ pip freeze | grep SQLAlchemy
SQLAlchemy==1.1.9
资料来源:
import logging
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
backref,
class_mapper,
relationship,
scoped_session,
sessionmaker)
from sqlalchemy.orm.session import Session
from sqlalchemy import (
create_engine,
inspect,
Column,
Integer,
ForeignKey,
MetaData)
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Base = declarative_base()
class Parent(Base):
__tablename__ = "parents"
id = Column(Integer, primary_key=True)
class Child(Base):
__tablename__ = "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id"))
parent = relationship(
"Parent",
backref=backref("child", uselist=False),
single_parent=True,
cascade="all")
engine = create_engine("sqlite://", echo=True)
metadata = MetaData()
with engine.begin() as connection:
metadata = Base.metadata
metadata.create_all(connection)
session = scoped_session(sessionmaker(bind=engine))
@listens_for(Base, "before_update", propagate=True)
def before_update(mapper, connection, target):
session = Session.object_session(target)
if session.is_modified(target, include_collections=False):
logger.info(
"target of type %s with id %s was modified",
type(target).__name__,
getattr(target, "id", None))
inspr = inspect(target)
attrs = class_mapper(target.__class__).column_attrs
for attr in attrs:
hist = getattr(inspr.attrs, attr.key).history
logger.info(
"attribute %s has changes: %s",
attr.key,
hist.has_changes())
parent = Parent()
session.add(parent)
session.flush()
child = Child(parent=parent)
session.add(child)
session.flush()
相关输出:
2017-11-29 18:07:28,471 INFO sqlalchemy.engine.base.Engine COMMIT
INFO:sqlalchemy.engine.base.Engine:COMMIT
2017-11-29 18:07:28,476 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
2017-11-29 18:07:28,477 INFO sqlalchemy.engine.base.Engine INSERT INTO parents DEFAULT VALUES
INFO:sqlalchemy.engine.base.Engine:INSERT INTO parents DEFAULT VALUES
2017-11-29 18:07:28,478 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2017-11-29 18:07:28,480 INFO sqlalchemy.engine.base.Engine SELECT children.id AS children_id, children.parent_id AS children_parent_id
FROM children
WHERE ? = children.parent_id
INFO:sqlalchemy.engine.base.Engine:SELECT children.id AS children_id, children.parent_id AS children_parent_id
FROM children
WHERE ? = children.parent_id
2017-11-29 18:07:28,480 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
INFO:__main__:target of type Parent with id 1 was modified
INFO:__main__:attribute id has changes: False
2017-11-29 18:07:28,483 INFO sqlalchemy.engine.base.Engine INSERT INTO children (parent_id) VALUES (?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO children (parent_id) VALUES (?)
2017-11-29 18:07:28,484 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
编辑:将Child
的backref重命名为Parent
从Parent
重命名为Child
Parent
的列未被修改,但其关系已被修改,即Parent.Parent
[sic]现在包含Child
,当您将child
添加到会话中时,我明白了--我认为include\u collections=False
参数可以解释这一点。话虽如此,检查是否向数据库发送“更新”的最佳方法是什么?检查对象并迭代其所有列属性?include_collections=False
表示它不查看集合,但parent.child
不是集合,因为您指定了uselist=False
。您只检查列属性的历史记录的方法应该有效——不管怎样,SQLAlchemy就是这样做的。@univerio您能写下来作为答案吗?