由于延迟加载,带有Pony ORM的DatabaseSessionIsOver?

kas*_*rhj 5 python orm ponyorm

我使用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
Run Code Online (Sandbox Code Playgroud)

当我在Jinja2中需要结果时 - 看起来像这样

{% for order in res %}
    Supplier: {{ order.supplier.name }}
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

我得到了

DatabaseSessionIsOver: Cannot load attribute Supplier[3].name: the database session is over
Run Code Online (Sandbox Code Playgroud)

如果我取消注释该for r in res部分,它可以正常工作.我怀疑有某种延迟加载不会被加载res = q2[:].我完全错过了一个观点或者这里发生了什么?

Ale*_*sky 7

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

现在你可以写:

q = q.prefetch(Supplier)
Run Code Online (Sandbox Code Playgroud)

要么

q = q.prefetch(Order.supplier)
Run Code Online (Sandbox Code Playgroud)

和Pony会自动加载相关supplier对象.

下面我将使用标准的Pony示例与学生,小组和部门一起显示几个预取查询.

from pony.orm.examples.presentation import *
Run Code Online (Sandbox Code Playgroud)

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

students = select(s for s in Student)[:]
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

作为prefetch()方法的参数,您可以指定实体和/或属性.如果指定了实体,则将预取具有此类型的所有to-one属性.如果指定了属性,则将预取此特定属性.只有在显式指定时才会预取to-many属性(如Student.courses示例中所示).预取以递归方式进行,因此您可以加载长链属性,例如student.group.dept.

当预取对象时,默认情况下会加载其所有属性,但惰性属性和多对多属性除外.如果需要,您可以显式预取lazy和to-many属性.

我希望这种新方法完全涵盖您的用例.如果某些内容无法正常工作,请在GitHub上启动新问题.您还可以在Pony ORM邮件列表中讨论功能并提出功能请求.

PS我不确定你使用的存储库模式会给你带来什么好处.我认为它实际上增加了模板渲染和repo实现之间的耦合,因为当模板代码开始使用新属性时,您可能需要更改repo实现(即将新实体添加到预取列表).使用顶级@db_session装饰器,您只需将查询结果发送到模板,所有操作都会自动发生,而无需显式预取.但也许我错过了一些东西,所以我很想看到关于在你的情况下使用存储库模式的好处的其他评论.


Ale*_*ich 5

发生这种情况是因为您尝试访问未加载的相关对象,并且因为您尝试在数据库会话之外访问它(使用该函数修饰的函数db_session),Pony会引发此异常.

建议的方法是db_session在顶层使用装饰器,在放置Flask app.route装饰器的同一位置:

@app.route('/index')
@db_session
def index():
    ....
    return render_template(...)
Run Code Online (Sandbox Code Playgroud)

这样,对数据库的所有调用都将包含在数据库会话中,该数据库会话将在生成网页后完成.

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

---更新:

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

for r in res:
    r.supplier.name 
Run Code Online (Sandbox Code Playgroud)

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

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