如何在 SQLAlchemy 中使用动态关系映射层次结构?

Posted

技术标签:

【中文标题】如何在 SQLAlchemy 中使用动态关系映射层次结构?【英文标题】:How can I map a hierarchy in SQLAlchemy with dynamic relationships? 【发布时间】:2010-08-26 20:51:50 【问题描述】:

我正在定义这样的 SQLAlchemy 模型:

class SubProject(Base):
  active = Column(Boolean)

class Project(Base):
  active = Column(Boolean)
  subprojects = relationship(SubProject, backref=backref('project'))

class Customer(Base):
  active = Column(Boolean)
  projects = relationship(Project, backref=backref('customer'))

我需要获取以下两种状态之一的客户列表:

    所有客户,所有项目和所有子项目

    只有活跃的客户,只有活跃的项目,只有活跃的子项目

    编辑值得注意的是,应该包括所有没有项目的活跃客户, 并且所有没有活动调查的活动项目都应包含在其中。

这在带有连接的 SQL 中是微不足道的,但我不知道如何使用 SQLAlchemy ORM 来完成它。这里有什么解决办法?

【问题讨论】:

【参考方案1】:

如果我理解正确,您需要使所有非活动对象对您的查询不可见。以下类将过滤掉所有 active 属性设置为 False 的模型对象,包括通过关系访问的模型对象:

from sqlalchemy.orm import Query
from sqlalchemy.orm.util import _class_to_mapper


class QueryActive(Query):

    def __init__(self, entities, *args, **kwargs):
        Query.__init__(self, entities, *args, **kwargs)
        query = self
        for entity in entities:
            if hasattr(entity, 'parententity'):
                entity = entity.parententity
            cls = _class_to_mapper(entity).class_
            if hasattr(cls, 'active'):
                query = query.filter(cls.active==True)
        self._criterion = query._criterion

    def get(self, ident):
        # Use default implementation when there is no condition
        if not self._criterion:
            return Query.get(self, ident)
        # Copied from Query implementation with some changes.
        if hasattr(ident, '__composite_values__'):
            ident = ident.__composite_values__()
        mapper = self._only_mapper_zero(
                    "get() can only be used against a single mapped class.")
        key = mapper.identity_key_from_primary_key(ident)
        if ident is None:
            if key is not None:
                ident = key[1]
        else:
            from sqlalchemy import util
            ident = util.to_list(ident)
        if ident is not None:
            columns = list(mapper.primary_key)
            if len(columns)!=len(ident):
                raise TypeError("Number of values doesn't match number "
                                'of columns in primary key')
            params = 
            for column, value in zip(columns, ident):
                params[column.key] = value
            return self.filter_by(**params).first()

要使用它,您必须创建一个单独的会话对象:

session_active = sessionmaker(bind=engine, query_cls=QueryActive)()

这种方法有局限性,不适用于一些复杂的查询,但适用于大多数项目。

【讨论】:

这很有希望,但会导致我的应用程序出现一系列异常,例如:InvalidRequestError: Query.get() being called on a Query with existing criterion. 是的,这根本行不通;它最终污染了我的应用程序中的所有其他查询。 你能提供一个失败的最小测试用例吗?您的错误消息就像您复制了没有 get() 方法的 QueryActive 类的一部分。【参考方案2】:

要增加 Denis 的答案,您可以使用 enable_assertions(False)。据我所知,这是对 SQLAlchemy 为正常操作添加的查询的额外检查。对于更复杂的情况,您可以禁用它。

【讨论】:

以上是关于如何在 SQLAlchemy 中使用动态关系映射层次结构?的主要内容,如果未能解决你的问题,请参考以下文章

Django ORM和SQLAlchemy类比

SQLAlchemy-介绍安装

SQLAlchemy - 将自引用关系映射为一对多(声明形式)

SQLAlchemy怎么在Oracle中自动建立映射

SQLAlchemy

我可以在 SQLAlchemy ORM 中使用动态关系(或视图)吗?