【问题标题】:How to explicitly load relationships on an existing object?如何在现有对象上显式加载关系?
【发布时间】:2015-11-27 01:31:06
【问题描述】:

我有一个 SQLAlchemy 模型 Foo,其中包含一个延迟加载关系 bar,它指向另一个也具有延迟加载关系 foobar 的模型。

正常查询时,我会使用此代码来确保所有对象都使用单个查询加载:

session.query(Foo).options(joinedload('bar').joinedload('foobar'))

但是,现在我有一个基类已经为我提供了一个使用 session.query(Foo).one() 检索的 Foo 实例的情况,因此关系是延迟加载的(这是默认值,我不想改变它)。

对于单层嵌套,我不介意在访问 foo.bar 时加载它,但由于我还需要访问 foo.bar[x].foobar,所以我更愿意避免在循环中发送查询(每当我访问foobar)。

我正在寻找一种方法让 SQLAlchemy 加载 foo.bar 关系,同时还为 foobar 使用 joinedload 策略。

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    我最近遇到了类似的情况,最后做了以下事情:

    eager_loaded = db.session.query(Bar).options(joinedload('foobar'))
        .filter_by(bar_fk=foo.foo_pk).all()
    

    假设您可以在filter_by 参数中重新创建bar 连接条件,则集合中的所有对象都将加载到身份映射中,而foo.bar[x].foobar 将不需要进入数据库。

    一个警告:如果它们不再被强引用,身份映射可能会处理加载的实体 - 因此分配给eager_loaded

    【讨论】:

      【解决方案2】:

      SQLAlchemy wiki 包含 Disjoint Eager Loading 配方。对父集合发出查询,然后查询并组合子集合。在大多数情况下,这在 SQLAlchemy 中作为 subquery 策略实现,但该配方涵盖了您稍后明确需要进行查询的情况,而不仅仅是单独进行。

      这个想法是您对子查询进行排序并通过链接关系的远程列对结果进行分组,然后使用子组填充每个父项的属性。以下是从配方中略微修改的,以允许传入带有额外选项的自定义子查询,而不是从父查询构建它。这确实意味着您必须更仔细地构建子查询:如果您的父查询有过滤器,那么子查询也应该加入和过滤,以防止加载不需要的行。

      from itertools import groupby
      from sqlalchemy.orm import attributes
      
      def disjoint_load(parents, rel, q):
          local_cols, remote_cols = zip(*rel.prop.local_remote_pairs)
          q = q.join(rel).order_by(*remote_cols)
      
          if attr.prop.order_by:
              q = q.order_by(*rel.prop.order_by)
      
          collections = dict((k, list(v)) for k, v in groupby(q, lambda x: tuple([getattr(x, c.key) for c in remote_cols])))
      
          for p in parents:
              attributes.set_committed_value(
                  p, attr.key,
                  collections.get(tuple([getattr(p, c.key) for c in local_cols]), ()))
      
          return parents
      
      # load the parents
      devices = session.query(Device).filter(Device.active).all()
      
      # build the child query with extras, use the same filter
      findings = session.query(Finding
      ).join(Device.findings
      ).filter(Device.active
      ).options(db.joinedload(Finding.scans))
      
      for d in disjoint_load(devices, Device.findings, findings):
          print(d.cn, len(d.findings))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-18
        • 1970-01-01
        • 2014-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-12
        • 1970-01-01
        • 2021-11-06
        相关资源
        最近更新 更多