【问题标题】:Can SQLAlchemy's session.merge() update its result with newer data from the database?SQLAlchemy 的 session.merge() 可以使用数据库中的新数据更新其结果吗?
【发布时间】:2010-12-23 09:40:00
【问题描述】:

SQLAlchemy 文档说“session.merge() 协调实例的当前状态及其关联子项与数据库中的现有数据”。

现有对象的状态是否会被数据库中的新数据更新?如何?什么时候?

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    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() 没有在会话中找到具有相同身份的对象,因为我们已将其删除。我还创建了部分分配状态的新对象,但其余部分是从数据库加载的。

    【讨论】:

    • 感谢您的精彩解释,我对 merge() 背后的想法有一些阴影,现在我明白了
    【解决方案2】:

    我注意到关于合并的一件事是,即使访问未附加对象上的字段也会导致它在合并期间被修改。

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

    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'
    

    但如果我阅读名称或描述,就会发生这种情况

    x = X(id=1)
    print x.name                # None
    
    merged_x = session.merge(x)
    
    print merged_x.name         # None
    print merged_x.description  # 'bar'
    

    这是违反直觉的,我认为是不正确的。有没有办法关闭这种行为?显然,仅仅存在 __dict__ 中的属性就会导致这种情况发生。此“功能”应在文档中注明。

    【讨论】:

    • 我不确定我是否同意@shane 的编辑。你可以解释吗?我在原文中的意思是,如果我访问x.y,然后运行session.merge(x),那么x.y 将不会得到合并值。你的意思是在合并中将它“标记”为被修改(从而防止它被覆盖)?
    • 是的,这正是我的意思。我遇到了同样的问题,但不确定这里的措辞方式。我将“修改”表示为“持久化到数据库”,因为merged_x 现在代表新的持久化数据。在session.merge(x) 之后,x 的属性值根本没有被修改(也不是故意的),merged_x 是一个新对象。
    • 来自文档:The state of the given instance is then copied onto the located/newly created instance. For attributes which are present on the source instance, the value is transferred to the target instance. For mapped attributes which aren’t present on the source, the attribute is expired on the target instance, discarding its existing value
    • 老实说,我很久以前就问过这个问题,我已经忘记了上下文,但是如果源是指数据库中的数据(合并的数据),那么这似乎是错误的。我的意思是,如果我将某些内容合并到一个我没有写入的字段中,我希望它会更新该字段。 (与 SELECT FOR UPDATE 不同。)。我创建了一个 id=1 的对象。我访问该对象上的 .name(它的值为 None)。我合并数据库数据(其中值为非无)。我希望合并更新.name。此行为与数据库读取通常发生的方式一致。
    • 答案中提到的__dict__ 行为现在记录在here
    猜你喜欢
    • 2010-12-17
    • 2023-03-17
    • 2010-09-21
    • 1970-01-01
    • 2017-04-08
    • 2015-08-02
    • 2023-03-27
    相关资源
    最近更新 更多