3

質問

プロパティアクセスはSQLAlchemyでセッションフラッシュをトリガーできますか? 私の期待は、たとえば、 column_property() または @hybrid_property を介してオブジェクトにアタッチされたクエリが、session.Query() を介して作成されたクエリと同じ方法でセッションの自動フラッシュを引き起こすことです。そうではないようです。

以下の簡単な例では、Account に Entry コレクションが含まれています。また、select-sum クエリを公開する column_property() で構築された「バランス」プロパティも提供します。session.flush() が明示的に呼び出された場合にのみ、新しいエントリがアカウントの残高に表示されます。

この動作は最適ではないようです。Account クラスのユーザーは、balance 実装の内部構造を理解していることに基づいて、コード全体に flush() 呼び出しを散りばめる必要があります。実装が変更された場合 (たとえば、「balance」が以前は Python の @property であった場合)、Account インターフェイスが本質的に同じであっても、バグが発生する可能性があります。代替手段はありますか?

完全な例

import sys
import sqlalchemy as sa
import sqlalchemy.sql
import sqlalchemy.orm
import sqlalchemy.ext.declarative

Base = sa.ext.declarative.declarative_base()

class Entry(Base):
    __tablename__ = "entries"

    id = sa.Column(sa.Integer, primary_key=True)
    value = sa.Column(sa.Numeric, primary_key=True)
    account_id = sa.Column(sa.Integer, sa.ForeignKey("accounts.id"))
    account = sa.orm.relationship("Account", backref="entries")

class Account(Base):
    __tablename__ = "accounts"

    id = sa.Column(sa.Integer, primary_key=True)
    balance = sa.orm.column_property(
        sa.sql.select([sa.sql.func.sum(Entry.value)])
            .where(Entry.account_id == id)
        )

def example(database_url):
    # connect to the database and prepare the schema
    engine = sa.create_engine(database_url)
    session = sa.orm.sessionmaker(bind=engine)()

    Base.metadata.create_all(bind = engine)

    # add an entry to an account
    account = Account()

    account.entries.append(Entry(value = 42))

    session.add(account)

    # and look for that entry in the balance
    print "account.balance:", account.balance

    assert account.balance == 42

if __name__ == "__main__":
    example(sys.argv[1])

観察された出力

$ python sa_column_property_example.py postgres:///za_test
account.balance: None
Traceback (most recent call last):
  File "sa_column_property_example.py", line 46, in <module>
    example(sys.argv[1])
  File "sa_column_property_example.py", line 43, in example
    assert account.balance == 42
AssertionError

優先出力

session.flush() への明示的な呼び出しを追加せずに、「account.balance: 42」を見たいと思います。

4

1 に答える 1

4

column_propertyは、クエリ時、つまりquery(Account)と言ったとき、および属性の有効期限が切れたとき、つまりsession.expire( "account"、['balance'])と言ったときにのみ評価されます。

属性が毎回クエリを呼び出すようにするには、@ property(ここでは、スクリプトがsqliteで機能するためのいくつかの小さなmod)を使用します。

import sys
import sqlalchemy as sa
import sqlalchemy.sql
import sqlalchemy.orm
import sqlalchemy.ext.declarative

Base = sa.ext.declarative.declarative_base()

class Entry(Base):
    __tablename__ = "entries"

    id = sa.Column(sa.Integer, primary_key=True)
    value = sa.Column(sa.Numeric)
    account_id = sa.Column(sa.Integer, sa.ForeignKey("accounts.id"))
    account = sa.orm.relationship("Account", backref="entries")

class Account(Base):
    __tablename__ = "accounts"

    id = sa.Column(sa.Integer, primary_key=True)

    @property
    def balance(self):
        return sqlalchemy.orm.object_session(self).query(
                    sa.sql.func.sum(Entry.value)
                ).filter(Entry.account_id == self.id).scalar()

def example(database_url):
    # connect to the database and prepare the schema
    engine = sa.create_engine(database_url, echo=True)
    session = sa.orm.sessionmaker(bind=engine)()

    Base.metadata.create_all(bind = engine)

    # add an entry to an account
    account = Account()

    account.entries.append(Entry(value = 42))

    session.add(account)

    # and look for that entry in the balance
    print "account.balance:", account.balance

    assert account.balance == 42

if __name__ == "__main__":
    example("sqlite://")

「フラッシング」自体は、一般的に私たちが心配しなければならないことではないことに注意してください。自動フラッシュ機能は、query()がデータベースにアクセスして結果を取得するたびにフラッシュが呼び出されることを保証するため、実際には、目的のクエリが確実に発生します。

この問題に対する別のアプローチは、ハイブリッドを使用することです。各アプローチのトレードオフをリストした、マップされた属性としてのSQL式の3つのメソッドすべての概要を読むことをお勧めします。

于 2013-02-13T20:02:29.643 に答える