【问题标题】:Exposing SQLAlchemy ORM detached entities as domain objects将 SQLAlchemy ORM 分离的实体公开为域对象
【发布时间】:2018-08-12 05:38:35
【问题描述】:

这篇文章很长,所以我把它分成主要问题,一个带有代码的说明性示例,最后描述了我的想法和问题。

问题

我在整个应用程序中都使用了分离的实体。这是向应用程序的其余部分公开 ORM 实体的正确方法吗?如果不是,有什么问题?

代码(设置)

数据库设置:

db_conn_string = "sqlite://"
# db_conn_string = "mysql+pymysql://root:root@33.33.33.1:33060/alchemist"
engine = create_engine(db_conn_string)
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine, expire_on_commit=False)

对于实体,我只在急切加载它们时才定义关系。这是为了确保当它们被用作分离实体时,您不能直接检索未加载的相关实体。

实体(截断):

class _Base(object):
    id = Column(Integer, primary_key=True)


Base = declarative_base(cls=_Base)


class Tree(Base):
    type = Column(String(200))

    branches = relationship("Branch", back_populates="tree", lazy='subquery')

    def __repr__(self):
        return "<Tree(id='{}', type='{}', branches='{}')>".format(self.id, self.type, self.branches)


class Branch(Base):
    name = Column(String(200))

    tree_id = Column(Integer, ForeignKey('tree.id'), nullable=False)
    tree = relationship("Tree", back_populates="branches", lazy='subquery')

    # Not defining leaves since this is not an eager loaded relationship and must be retrieved explicitly.
    # leaves = relationship("Leaf", back_populates="branch")

    def __repr__(self):
        return "<Branch(id='{}', name='{}', tree_id='{}')>".format(self.id, self.name, self.tree_id)


class Leaf(Base):
    size = Column(Integer)

    branch_id = Column(Integer, ForeignKey('branch.id'), nullable=False)
    # branch = relationship("Branch")  # Also not eager loaded

    def __repr__(self):
        return "<Leaf('{}')>".format(self.size)

我提供了一个存储库对象来执行基本的 CRUD 操作。然后在存储库周围的上下文对象中管理会话事务范围:

class RepositoryContext(object):
    def __init__(self, entity_class):
        self.entity_class = entity_class

    def __enter__(self):
        self.session = get_session()
        return CrudRepository(self.entity_class, self.session)

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.session.commit()
        except Exception:
            self.session.rollback()
            raise
        finally:
            self.session.close()


class CrudRepository(object):
    """
    CrudRepository implements (entity agnostic) CRUD operations.
    """

    def __init__(self, entity_class, session):
        self.entity_class = entity_class
        self._session = session

    def retrieve_all(self):
        return self._session.query(self.entity_class).all()

    def retrieve_by(self, **kwargs):
        return self._session.query(self.entity_class).filter_by(**kwargs).one_or_none()

    # Other CRUD methods here.

    def query(self):
        '''To perform custom queries'''
        return self._session.query(self.entity_class)

代码(用法)

所以基本用法如下所示:

with RepositoryContext(Tree) as repo:
    tree = repo.retrieve_by(id=1)
# Session is closed outside of context. tree is a detached instance.
len(tree.branches)  # This is ok, eager loaded

如果您想获得某个分支的叶子,我们无法使用tree.branches[0].leaves 访问它,因为未定义该关系。因为我们不想急切加载它,所以我们必须单独检索它,如下所示:

with RepositoryContext(Leaf) as repo:
    branch = tree.branches[0]
    leaves = repo.query().filter_by(branch_id=branch.id).all()
# Now have leaves related to first branch. Also detached

类似的更新或刷新你会打开一个上下文并调用相关的函数:

with RepositoryContext(Tree) as repo:
    repo.refresh(tree)  # This refreshes the tree state (discarding changes)

上下文

对于一个新项目,我们正在使用 SQLAlchemy ORM,但我不确定如何正确使用 ORM 实体。

我一直被教导应该尽可能地将域模型和数据库细节彼此分开。然而,对于 ORM,这意味着我必须不断地将 ORM 实体映射到域模型对象,然后再返回,这首先破坏了使用 ORM 的全部意义。

在应用程序中使用 ORM 实体的过程中,我想确保不会出现意外的副作用。因此,我创建了一个紧密的事务范围来强制任何持久性都由存储库类显式处理。这意味着我在整个应用程序中使用分离的实体,并且只在事务中附加它们。

在我当前的实现中,我失去了 SQLAlchemy 提供的一些功能。那是访问尚未预先加载的相关数据(例如,从代码示例中:branch.leaves 未定义,因此您必须通过指定分支 id 来添加它们)。

如果帖子很长,我深表歉意,我已尽可能截断,同时保持它可运行。

【问题讨论】:

    标签: python orm architecture sqlalchemy


    【解决方案1】:

    对于那些正在寻找更多信息/指南的人:Mike Bayer 在https://groups.google.com/forum/#!topic/sqlalchemy/u9Igta1CYdo 写了一个非常详细的回答我的问题。

    【讨论】:

    • 感谢您发布后续消息,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-19
    • 2011-01-22
    • 1970-01-01
    • 2017-06-12
    • 1970-01-01
    • 2011-07-03
    • 2012-03-27
    相关资源
    最近更新 更多