web-dev-qa-db-ja.com

SQLAlchemyのsession.merge()は、データベースからの新しいデータで結果を更新できますか?

SQLAlchemyのドキュメントには、「 session.merge() インスタンスとそれに関連付けられた子の現在の状態をデータベース内の既存のデータと照合する」と記載されています。

既存のオブジェクトの状態がデータベースからの新しいデータによって更新されることはありますか?どうやって?いつ?

17
joeforker

SQLAlchemyは、セッション内の各IDを持つ単一のオブジェクトを持つように設計されています。ただし、既知のIDでオブジェクトを再作成する必要がある場合があります。ネットワークから取得するとき、または長いトランザクションを回避するためにオフラインロックを実装するとき。また、データベースに存在する可能性のある既知のIDを持つオブジェクトを作成すると、セッションがすでにこのIDを持つオブジェクトを追跡している可能性があります。それがmerge()メソッドの目的です。セッションにアタッチされたオブジェクトを返すため、セッション内で同じIDを持つ重複オブジェクトを回避できます。以下は、何が起こっているかを示す例です。

_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)は、同じIDを持つオブジェクト(上記で作成した_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()は、オブジェクトを消去したため、セッションで同じIDを持つオブジェクトを見つけられませんでした。また、状態が部分的に割り当てられた新しいオブジェクトを作成しましたが、残りはデータベースからロードされます。

53
Denis Otkidach

マージについて私が気づいたことの1つは、アタッチされていないオブジェクトのフィールドにアクセスしても、マージ中にそのフィールドが変更されることです。

たとえば、単純なクラスがある場合

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__に属性が存在するだけでこれが発生するようです。この「機能」は、ドキュメントに記載されている必要があります。

12
Adam Donahue