Django ORM:缓存和操作ForeignKey对象

发布于 2024-07-15 13:00:31 字数 1171 浏览 10 评论 0原文

考虑以下太空征服游戏的 models.py 框架:

class Fleet(models.Model):
    game = models.ForeignKey(Game, related_name='planet_set')
    owner = models.ForeignKey(User, related_name='planet_set', null=True, blank=True)
    home = models.ForeignKey(Planet, related_name='departing_fleet_set')
    dest = models.ForeignKey(Planet, related_name='arriving_fleet_set')
    ships = models.IntegerField()

class Planet(models.Model):
    game = models.ForeignKey(Game, related_name='planet_set')
    owner = models.ForeignKey(User, related_name='planet_set', null=True, blank=True)
    name = models.CharField(max_length=250)
    ships = models.IntegerField()

我正在处理的项目有许多这样的数据模型,并且我根据各种数据对象之间有些复杂的交互来更改游戏的状态。 我想避免对数据库进行大量不必要的调用,因此每回合我都会执行以下操作:

  1. 从数据库中查询所有舰队、行星和其他对象,并将它们缓存为 python 对象;
  2. 处理游戏对象,解析游戏对象的状态。游戏
  3. 将它们保存回数据库

当使用foreignkey对象时,这个模型似乎完全崩溃了。 例如,当一支新舰队离开一个行星时,我有一条看起来像这样的行:

fleet.home.ships -= fleet.ships

这条线运行后,我有其他代码可以改变每个行星上的船只数量,包括行星fleet.home。 不幸的是,上面一行中所做的更改并未反映在我之前获得的行星查询集中,因此当我在回合结束时保存所有行星时,对fleet.home船舶的更改会被覆盖。

有没有更好的方法来处理这种情况? 或者这就是所有 ORM 的本质吗?

Consider the following skeleton of a models.py for a space conquest game:

class Fleet(models.Model):
    game = models.ForeignKey(Game, related_name='planet_set')
    owner = models.ForeignKey(User, related_name='planet_set', null=True, blank=True)
    home = models.ForeignKey(Planet, related_name='departing_fleet_set')
    dest = models.ForeignKey(Planet, related_name='arriving_fleet_set')
    ships = models.IntegerField()

class Planet(models.Model):
    game = models.ForeignKey(Game, related_name='planet_set')
    owner = models.ForeignKey(User, related_name='planet_set', null=True, blank=True)
    name = models.CharField(max_length=250)
    ships = models.IntegerField()

I have many such data models for a project I'm working on, and I change the state of the game based on somewhat complicated interactions between various data objects. I want to avoid lots of unnecessary calls to the database, so once per turn, I do something like

  1. Query all the fleets, planets, and other objects from the database and cache them as python objects
  2. Process the game objects, resolving the state of the game
  3. Save them back in the database

This model seems to totally break down when using ForeignKey objects. For example, when a new fleet departs a planet, I have a line that looks something like this:

fleet.home.ships -= fleet.ships

After this line runs, I have other code that alters the number of ships at each of the planets, including the planet fleet.home. Unfortunately, the changes made in the above line are not reflected in the QuerySet of planets that I obtained earlier, so that when I save all the planets at the end of the turn, the changes to fleet.home's ships get overwritten.

Is there some better way of handling this situation? Or is this just how all ORMs are?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

自由如风 2024-07-22 13:00:31

Django 的 ORM 没有实现 身份映射(它位于 票务跟踪器,但尚不清楚是否或何时实施;至少有一个核心 Django 提交者拥有 表示反对)。 这意味着,如果您通过两个不同的查询路径到达同一个数据库对象,那么您将使用内存中的不同 Python 对象。

这意味着您的设计(一次将所有内容加载到内存中,修改很多内容,然后最后将其全部保存回来)使用 Django ORM 是行不通的。 首先,因为它通常会浪费大量内存加载到同一对象的重复副本中,其次是因为“覆盖”问题,例如您遇到的问题。

您要么需要重新设计以避免这些问题(要么小心一次只使用一个查询集,在进行另一个查询之前保存修改的任何内容;或者如果您加载多个查询,请手动查找所有关系,不要曾经使用外键的方便属性来遍历它们),或者使用实现身份映射的替代Python ORM。 SQLAlchemy 是一种选择。

请注意,这并不意味着 Django 的 ORM 是“糟糕的”。 它针对 Web 应用程序的情况进行了优化,此类问题很少见(我使用 Django 进行 Web 开发已有多年,但在实际项目中从未遇到过此问题)。 如果您的用例不同,您可能需要选择不同的 ORM。

Django's ORM does not implement an identity map (it's in the ticket tracker, but it isn't clear if or when it will be implemented; at least one core Django committer has expressed opposition to it). This means that if you arrive at the same database object through two different query paths, you are working with different Python objects in memory.

This means that your design (load everything into memory at once, modify a lot of things, then save it all back at the end) is unworkable using the Django ORM. First because it will often waste lots of memory loading in duplicate copies of the same object, and second because of "overwriting" issues like the one you're running into.

You either need to rework your design to avoid these issues (either be careful to work with only one QuerySet at a time, saving anything modified before you make another query; or if you load several queries, look up all relations manually, don't ever traverse ForeignKeys using the convenient attributes for them), or use an alternative Python ORM that implements identity map. SQLAlchemy is one option.

Note that this doesn't mean Django's ORM is "bad." It's optimized for the case of web applications, where these kinds of issues are rare (I've done web development with Django for years and never once had this problem on a real project). If your use case is different, you may want to choose a different ORM.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文