SQLAlchemy的session.merge()能使用数据库中的新数据更新结果吗?

33 投票
2 回答
72328 浏览
提问于 2025-04-15 16:42

SQLAlchemy的文档上说,"session.merge() 会把一个实例当前的状态和它相关的子对象的状态与数据库中已有的数据进行对比和合并。"

那么,数据库中的新数据会不会更新现有对象的状态呢?怎么更新?什么时候更新?

2 个回答

13

我注意到一个关于合并(merge)的事情,就是即使是访问一个未连接对象的某个字段,也会在合并过程中导致它被修改。

举个例子,如果我有一个简单的类:

class X(Base):
    __tablename__= 'x'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    value = Column(String)

还有一行数据:

(1, 'foo', 'bar')

那么接下来这个合并看起来是很顺利的:

x = X(id=1)
merged_x = session.merge(x)

print merged_x.name         # 'foo'
print merged_x.description  # 'bar'

但是如果我只是读取一下名字(name)或描述(description),就会出现以下情况:

x = X(id=1)
print x.name                # None

merged_x = session.merge(x)

print merged_x.name         # None
print merged_x.description  # 'bar'

这让我觉得很不合理,而且我认为这是错误的。有没有办法关闭这种行为?显然,只要在 __dict__ 中有一个属性,就会导致这种情况发生。这个“特性”应该在文档中说明一下。

81

SQLAlchemy的设计是让每个身份在会话中只有一个对象。但是有时候你需要重新创建一个已知身份的对象,比如当你从网络获取数据时,或者当你实现离线锁以避免长时间的事务时。当你创建一个可能已经在数据库中存在的已知身份的对象时,会话中可能已经跟踪了一个具有相同身份的对象。这就是merge()方法的作用:它会返回一个附加到会话中的对象,从而避免会话中出现相同身份的重复对象。下面是一个例子,说明了发生了什么:

from sqlalchemy import *
from sqlalchemy.orm import *

metadata = MetaData()

t = Table(
    't', metadata,
    Column('id', Integer, primary_key=True),
    Column('state', String(10)),
)

class Model(object): pass

mapper(Model, t)

engine = create_engine('sqlite://')
metadata.create_all(engine)

session = sessionmaker(bind=engine)()

obj1 = Model()
obj1.state = 'value1'
session.add(obj1)
session.commit()
obj_id = obj1.id

obj2 = Model()
obj2.id = obj_id
obj2.state = 'value2'
obj3 = session.merge(obj2)
session.commit()
print obj3 is obj1, obj3 is obj2
print obj3.state

输出结果是:

True False
value2

因此,session.merge(obj2)发现会话中已经有一个相同身份的对象(就是上面创建的obj1),所以它将obj2的状态合并到已有的对象中,并返回这个对象。

下面是另一个例子,展示了如何从数据库加载状态:

# ...skipped...

t = Table(
    't', metadata,
    Column('id', Integer, primary_key=True),
    Column('state1', String(10)),
    Column('state2', String(10)),
)

# ...skipped...

obj1 = Model()
obj1.state1 = 'value1-1'
obj1.state2 = 'value2-1'
session.add(obj1)
session.commit()
obj_id = obj1.id
session.expunge_all()

obj2 = Model()
obj2.id = obj_id
obj2.state1 = 'value1-2'
obj3 = session.merge(obj2)
session.commit()
print obj3 is obj1, obj3 is obj2
print obj3.state1, obj3.state2

输出结果是:

False False
value1-2 value2-1

现在merge()没有在会话中找到相同身份的对象,因为我们已经将其移除。此外,我创建了一个状态部分赋值的新对象,但其余部分是从数据库加载的。

撰写回答