我正在使用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[:]
。
我完全错过了一个观点或者发生了什么事吗?
答案 0 :(得分:6)
我刚添加了预取功能,可以解决您的问题。您可以从GitHub repository获取工作代码。此功能将成为即将发布的Pony ORM 0.5.4的一部分。
现在你可以写:
q = q.prefetch(Supplier)
或
q = q.prefetch(Order.supplier)
并且Pony会自动加载相关的supplier
个对象。
下面我将使用标准的Pony示例与学生,小组和部门一起展示几个预取查询。
from pony.orm.examples.presentation import *
仅加载学生对象,不进行任何预取:
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()
方法的参数,您可以指定实体和/或属性。如果指定了实体,则将预取具有此类型的所有 to-one 属性。如果指定了属性,则将预取此特定属性。只有在显式指定时才会预取to-many属性(如Student.courses
示例中所示)。预取以递归方式进行,因此您可以加载长链属性,例如student.group.dept
。
当预取对象时,默认情况下会加载其所有属性,但惰性属性和多对多属性除外。如果需要,您可以显式预取lazy和to-many属性。
我希望这种新方法完全涵盖您的用例。如果某些内容无法按预期运行,请start new issue on GitHub。您还可以在Pony ORM mailing list讨论功能并发出功能请求。
P.S。我不确定您使用的存储库模式是否会给您带来很大的好处。我认为它实际上增加了模板渲染和repo实现之间的耦合,因为当模板代码开始使用新属性时,您可能需要更改repo实现(即将新实体添加到预取列表)。使用顶级@db_session
装饰器,您只需将查询结果发送到模板,所有操作都会自动发生,而无需显式预取。但也许我错过了一些东西,所以我很想看到关于在你的情况下使用存储库模式的好处的其他评论。
答案 1 :(得分:5)
这是因为您尝试访问未加载的相关对象,并且因为您尝试在数据库会话之外访问它(使用db_session
修饰的函数),Pony会引发此异常
推荐的方法是在顶层使用db_session
装饰器,在放置Flask的app.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
语句。当您触摸'r.supplier.name
属性时,Pony会加载相关supplier
对象的所有非延迟属性。如果您需要加载延迟属性,则需要单独触摸它们。
似乎我们需要引入一种方法来指定在查询执行期间应该加载哪些相关对象。我们将在以后的一个版本中添加此功能。