考虑以下用于空间征服游戏的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()
我正在处理的项目有很多这样的数据模型,我根据各种数据对象之间的复杂交互来改变游戏的状态。我想避免对数据库进行大量不必要的调用,所以每回合一次,我会做类似
的事情使用ForeignKey对象时,此模型似乎完全崩溃了。例如,当一支新舰队离开一颗行星时,我有一条看起来像这样的线:
fleet.home.ships -= fleet.ships
这条线路运行后,我还有其他代码可以改变每个行星上的船只数量,包括行星舰队。不幸的是,上面一行中所做的更改没有反映在我之前获得的行星的查询集中,因此当我在转弯结束时保存所有行星时,对fleet.home船只的更改将被覆盖。
有没有更好的方法来处理这种情况?或者这就是所有ORM的原因?
答案 0 :(得分:20)
Django的ORM没有实现identity map(它在ticket tracker中,但不清楚它是否或何时实现;至少有一个核心Django提交者expressed opposition to it )。这意味着如果通过两个不同的查询路径到达同一个数据库对象,则在内存中使用不同的Python对象。
这意味着您的设计(一次将所有内容加载到内存中,修改很多内容,然后将其全部保存到最后)使用Django ORM是行不通的。首先是因为它通常会浪费大量内存加载到同一个对象的重复副本中,其次是因为“覆盖”问题,比如你正在遇到的问题。
您需要重新设计您的设计以避免这些问题(要么小心一次只使用一个QuerySet,在进行另一个查询之前保存任何修改过的内容;或者如果您加载多个查询,请手动查找所有关系,不要使用方便的属性遍历ForeignKeys),或者使用实现身份映射的替代Python ORM。 SQLAlchemy是一种选择。
请注意,这并不意味着Django的ORM“糟糕”。它针对Web应用程序进行了优化,这些问题很少见(我已经用Django进行了多年的Web开发,而且从未在真正的项目中遇到过这个问题)。如果您的用例不同,您可能想要选择不同的ORM。
答案 1 :(得分:1)