我正在编写一个小的django命令来将数据从json API端点复制到Django数据库中。在我实际使用obj, created = model.objects.get_or_create(**filters)
创建对象时,我收到MultipleObjectsReturned
错误。这对我来说是令人惊讶的,因为我对get_or_create
的理解是,如果我尝试创建一个已经存在的对象,它将只是“得到”它。
我不确定我正在克隆的数据库的完整性,但即使其中有多个相同的对象,当我将它们加载到我的本地Django数据库时,也不应该get_or_create使它成为我从来没有获得多份副本?
有人可以解释一下吗?我很乐意提供更多细节,我只是不想让读者陷入困境。
答案 0 :(得分:22)
想象一下,您有以下型号:
class DictionaryEntry(models.Model):
name = models.CharField(max_length=255, null=False, blank=False)
definition = models.TextField(null=True, blank=False)
和以下代码:
obj, created = DictionaryEntry.objects.get_or_create(
name='apple', definition='some kind of fruit')
get_or_create
如果您还没有看到code for get_or_create
:
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
现在假设您有一个带有2
工作进程的Web服务器,它们都有自己的并发访问到数据库。
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False # <===== nope not there...
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
如果时机正确(或者错误取决于你想用它来表达的方式),两个进程都可以进行查找而不能找到项目。他们都可以创建项目。一切都很好......
MultipleObjectsReturned: get() returned more than one KeyValue -- it returned 2!
一切都很好......直到你第三次打电话给get_or_create
,“他们说第三次是魅力”。
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False # <==== kaboom, 2 objects.
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
unique_together
你怎么能解决这个问题?也许在数据库级别强制执行约束:
class DictionaryEntry(models.Model):
name = models.CharField(max_length=255, null=False, blank=False)
definition = models.TextField(null=True, blank=False)
class Meta:
unique_together = (('name', 'definition'),)
回到功能:
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True # <==== this handles IntegrityError
return instance, created
假设你和以前有相同的种族,他们都没有找到该项目并继续进行插入;这样做他们将开始交易,其中一个将赢得比赛,而另一个将看到IntegrityError
。
该示例使用TextField
,mysql
转换为LONGTEXT
(在我的情况下)。添加unique_together
约束会导致syncdb
失败。
django.db.utils.InternalError: (1170, u"BLOB/TEXT column 'definition' used in key specification without a key length")
所以,没有运气,你可能需要手动处理MultipleObjectsReturned
。
TextField
替换为CharField
。CharField
,TextField
可以是pre_save
的强哈希值,您可以在unique_together
中计算并在{{1}}中使用。< / LI>
答案 1 :(得分:3)
顾名思义,get_or_create
model.objects.get()
或model.objects.create()
s。
它在概念上等同于:
try:
model.objects.get(pk=1)
except model.DoesNotExist:
model.objects.create(pk=1)
您可以在源代码中找到这些类型问题的明确答案。提示:搜索def get_or_create
。
如您所见,此函数仅捕获try / except中的DoesNotExist
。
def get_or_create(self, **kwargs):
"""
Looks up an object with the given kwargs, creating one if necessary.
Returns a tuple of (object, created), where created is a boolean
specifying whether an object was created.
"""
assert kwargs, \
'get_or_create() must be passed at least one keyword argument'
defaults = kwargs.pop('defaults', {})
lookup = kwargs.copy()
for f in self.model._meta.fields:
if f.attname in lookup:
lookup[f.name] = lookup.pop(f.attname)
try:
self._for_write = True
return self.get(**lookup), False
except self.model.DoesNotExist:
答案 2 :(得分:3)
使用get_or_create()API可能导致MultipleObjectsReturned错误的另一种情况似乎是,如果有多个线程同时使用相同的查询参数集调用此API。
仅仅依靠try ... catch ...在Python中创建一个独特的行是行不通的。如果您尝试使用此API,我认为您应该在数据库中的相应列上具有匹配的唯一性约束。