8

考虑以下用于太空征服游戏的 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。不幸的是,上一行所做的更改并没有反映在我之前获得的行星查询集中,因此当我在回合结束时保存所有行星时,对舰队.home 的船只的更改会被覆盖。

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

4

2 回答 2

20

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

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

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

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

于 2009-03-25T15:16:03.853 回答
1

这也许是您正在寻找的:

https://web.archive.org/web/20121126091406/http://simonwillison.net/2009/May/7/mmalones/

于 2009-05-22T14:34:54.950 回答