6

我正在使用 Pony ORM 作为烧瓶解决方案,我遇到了以下问题。

考虑以下:

@db_session
def get_orders_of_the_week(self, user, date):
    q = select(o for o in Order for s in o.supplier if o.user == user)
    q2 = q.filter(lambda o: o.date >= date and o.date <= date+timedelta(days=7))
    res = q2[:]

    #for r in res:
    #    print r.supplier.name

    return res

当我需要 Jinja2 中的结果时——看起来像这样

{% for order in res %}
    Supplier: {{ order.supplier.name }}
{% endfor %}

我得到一个

DatabaseSessionIsOver: Cannot load attribute Supplier[3].name: the database session is over

如果我取消注释该for r in res部分,它工作正常。我怀疑有某种延迟加载没有加载res = q2[:]. 我完全错过了一点还是这里发生了什么?

4

2 回答 2

7

我刚刚添加了应该可以解决您的问题的预取功能。您可以从GitHub 存储库中获取工作代码。此功能将成为即将发布的 Pony ORM 0.5.4 的一部分。

现在你可以写:

q = q.prefetch(Supplier)

或者

q = q.prefetch(Order.supplier)

并且 Pony 会自动加载相关supplier的对象。

下面我将使用带有 Student、Groups 和 Departments 的标准 Pony 示例展示几个带有预取的查询。

from pony.orm.examples.presentation import *

仅加载 Student 对象,不进行任何预取:

students = select(s for s in Student)[:]

将学生与组和部门一起加载:

students = select(s for s in Student).prefetch(Group, Department)[:]

for s in students: # no additional query to the DB is required
    print s.name, s.group.major, s.group.dept.name

与上面相同,但指定属性而不是实体:

students = select(s for s in Student).prefetch(Student.group, Group.dept)[:]

for s in students: # no additional query to the DB is required
    print s.name, s.group.major, s.group.dept.name

加载学生及其课程(多对多关系):

students = select(s for s in Student).prefetch(Student.courses)

for s in students:
    print s.name
    for c in s.courses: # no additional query to the DB is required
        print c.name

作为prefetch()方法的参数,您可以指定实体和/或属性。如果您指定了一个实体,那么所有具有此类型的一对一属性都将被预取。如果您指定了一个属性,那么这个特定的属性将被预取。多对多属性仅在显式指定时才被预取(如Student.courses示例中所示)。预取是递归进行的,因此您可以加载长链属性,例如student.group.dept.

当对象被预取时,默认情况下它的所有属性都被加载,除了惰性属性和多对多属性。如果需要,您可以显式预取惰性和多对多属性。

我希望这种新方法完全涵盖您的用例。如果某些事情没有按预期工作,请在 GitHub 上开始新问题。您还可以在Pony ORM 邮件列表中讨论功能并提出功能请求。

PS 我不确定您使用的存储库模式是否会给您带来严重的好处。我认为它实际上增加了模板渲染和 repo 实现之间的耦合,因为当模板代码开始使用新属性时,您可能需要更改 repo 实现(即添加新实体到预取列表)。使用顶级@db_session装饰器,您只需将查询结果发送到模板,一切都会自动发生,无需显式预取。但也许我遗漏了一些东西,所以我有兴趣看到关于在您的案例中使用存储库模式的好处的更多评论。

于 2014-09-11T11:05:36.743 回答
5

发生这种情况是因为您试图访问未加载的相关对象,并且因为您试图在数据库会话之外访问它(用 装饰的函数db_session),Pony 引发了这个异常。

推荐的方法是db_session在顶层使用装饰器,与放置 Flaskapp.route装饰器的位置相同:

@app.route('/index')
@db_session
def index():
    ....
    return render_template(...)

这样,对数据库的所有调用都将与数据库会话一起包装,该会话将在生成网页后完成。

如果出于某种原因要将数据库会话缩小到单个函数,那么您需要在装饰有 的函数内迭代返回对象db_session并访问所有必要的相关对象。Pony 将使用最有效的方式从数据库中加载相关对象,避免 N+1 Query 问题。这样,Pony 将提取db_session范围内所有必要的对象,同时与数据库的连接仍然处于活动状态。

- - 更新:

现在,为了加载相关对象,您应该遍历查询结果并调用相关对象属性:

for r in res:
    r.supplier.name 

它类似于您示例中的代码,我只是删除了该print语句。当您“触摸”该属性时,Pony 会加载相关对象r.supplier.name的所有非惰性属性。supplier如果需要加载惰性属性,则需要分别触摸它们中的每一个。

似乎我们需要引入一种方法来指定在查询执行期间应该加载哪些相关对象。我们将在未来的某个版本中添加此功能。

于 2014-09-10T03:25:01.620 回答