如何在现有对象上显式加载关系?

时间:2015-09-01 17:48:11

标签: python sqlalchemy

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

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

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

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

对于单一级别的嵌套,我不会介意在访问foo.bar后加载它,但由于我还需要访问foo.bar[x].foobar我真的更愿意避免在循环中发送查询(每当我访问foobar时都会发生这种情况。)

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

2 个答案:

答案 0 :(得分:1)

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

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

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

一个警告:如果不再强烈引用已加载的实体,则看起来身份地图可能会处置它们 - 因此分配给eager_loaded

答案 1 :(得分:0)

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))