您的位置:

FieldError(f"Field {field.model._meta.object_name}.{field.name} cannot be both ""deferred and traversed using select_related at the same time.")的处理方案

  发布时间:2023-04-01 12:50:34
报错的原因表明在使用 select_related() 方法查询时,与之相关的字段已经被排除在延迟加载字段之外了。而 select_related() 方法则是查询时预先加载关联表中的数据,可以避免使用关联表时产生多余的数据库查询。还有一种解决方法就是使用代替 select_related(),因为它是支持与defer()和only() 一起使用的。

报错的原因

FieldError: "Field 'X' cannot be both deferred and traversed using select_related at the same time." 表明在使用 select_related() 方法查询时,与之相关的字段已经被排除在延迟加载字段之外(deferred)了。

Django 提供了一种延迟加载字段的机制,它可以通过使用 only() 或 defer() 方法来排除在查询中不需要使用的字段,这样可以减少数据库查询的次数,提高查询性能。

而 select_related() 方法则是查询时预先加载关联表中的数据,可以避免使用关联表时产生多余的数据库查询。

但是,你使用了 select_related() 方法时,若之前已经使用了 defer() 或者 only()方法排除了需要关联表中的字段,会导致这个错误。

解决方法就是取消 defer() 或 only()方法的使用,或者在 select_related() 中加入需要使用的字段。

还有一种解决方法就是使用 prefetch_related() 代替 select_related(),因为它是支持与defer()和only() 一起使用的。

如何解决

解决这个问题有以下几种方法:

1. 取消 defer() 或 only() 方法的使用, 使用 select_related() 时加入需要关联表中的字段


MyModel.objects.select_related('related_field').filter(...)

2. 使用 prefetch_related() 代替 select_related()


MyModel.objects.prefetch_related('related_field').filter(...)

3. 如果你需要更灵活的选择哪些字段需要延迟加载,你可以使用 Subquery() 或者 OuterRef(),这两个类都是在 Django 2.0 中引入的,用来解决这个问题


from django.db.models import Subquery, OuterRef
MyModel.objects.filter(pk=OuterRef('related_field__pk')).only('pk')
MyModel.objects.filter(pk=Subquery(MyModel.objects.filter(related_field__pk=OuterRef('pk')).values('pk')))

这几种方法任选其一即可解决此问题。注意使用 prefetch_related() 时会单独发一次数据库询问,如果数据量比较大可能会影响性能。

使用例子

第一种解决方法的例子:


class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

#之前
# Author.objects.defer('name').select_related('book') 
# 会报错 
#FieldError: Field 'name' cannot be both deferred and traversed using select_related at the same time

#可以改成
Author.objects.select_related('book') 
# 或
Author.objects.only('id').select_related('book')

第二种解决方法的例子:


#之前
# Author.objects.defer('name').select_related('book') 
# 会报错 
#FieldError: Field 'name' cannot be both deferred and traversed using select_related at the same time

#可以改成
Author.objects.prefetch_related('book') 

第三种解决方法的例子:


class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

from django.db.models import Subquery, OuterRef
books = Book.objects.filter(
    author=Subquery(Author.objects.filter(name='John Smith').values('id'))
)

books2 = Book.objects.filter(author__name='John Smith')

这只是为了给出大致的例子, 请根据实际情况进行调整,选择一种合适的方法解决问题。