Swift51.com
麦子学院 头像
麦子学院  2017-03-25 14:42

Django ORM模型的一点体会

回复:0  查看:2388  
使用Python Django 模型的话,一般都会用它自带的 ORM Object-relational mapping )模型。这个 ORM 模型的设计比较简单,学起来不会特别花时间。不过, Django ORM 模型有自己的一套语法,有时候会觉得别扭。这里聊一下我自己的体会,希望对大家 学习django有所帮助
   模型设计
  这一部分算处理得比较好的部分。Django 的数据模型的建立过程很简单,就是继承 django.db.models 中的 Model 类,然后给它增加属性。每一个属性可以对应关系数据库中的一个字段。比如在一个叫 myapp Django App 下,创建 models.py 文件:
   from django.db  import models class Person( models.Model):
  name = models.CharField( max_length=10)
  通过manage.py makemigrations migrate 命令,就可以执行数据库的迁移。上面的 name 属性,就对应了生成的 myapp_person 表中名为 "name" 的一列。这里的 max_length=10 对应了限制条件:
   VARCHAR(10)
  (在MySQL V4 中,代表了 10 个字节;在 MySQL V5 中,代表了 10 个字符。)
  除了上面的字符类型,其他常见的字段类型,在Django 都有对应的 *Field 来表达,比如 TextField DateField DateTimeField IntegerField DecimalField 。此外,还有一些常见的限制条件,除了上面的 max_length ,还有 default unique null primary_key 等等。数字类型的限制条件有 max min max_digits decimal_places 。这些限制条件都通过参数的形式传给属性。有一些限制条件是 Django 提供的,并没有数据库层面的对应物,比如 blank
  ( blank 参数为真时,对应字段可以为留为空白。 )
  在基本的模型设计上,Django ORM 没有留什么坑。
   关系
  Django 中的一对一、多对一、多对多关系可以通过下面方式表达:
   from django.db  import models
   class Company( models.Model):
  name = models.CharField( max_length=10)
   class Group( models.Model):
  name = models.CharField( max_length=10)
   class Person( models.Model):
  name = models.CharField( max_length=10)
   class Customer( models.Model):
  name    = models.CharField( max_length=10)
  person  = models.OneToOneField(Person)
  company = models.ForeignKey(Company,  on_delete= models.CASCADE)
  groups  = models.ManyToManyField(Group)
  Customer 的定义中,用到一对一、多对一、多对多关系。它们分别通过 OneToOneField ForeignKey ManyToManyField 来实现。
  需要注意的是,在Django ORM 中,只能通过 ForeignKey 来定义多对一关系,不能显示地定义一对多关系。但你可以使用模型对象的 *_set 语法来反向调用多对一关系。比如说:
   company.customer_set   #company 是一个 Company 的实例
  就可以根据一对多关系,调到该公司下的所有客户。此外,多对多关系也可以用类似的方式反向调用,比如:
   group.customer_set
  此外,你还可以在模型中加入related_name 参数,从而在反省调用时,改用 "*_set" 之外的其他名称,比如:
   class  Customer(models.Model):
  person  = models.OneToOneField(Person)
  address = models.CharField(max_length=100)
  company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="customers")
  如果两个模型之间有多个关系时,related_name 可以防止 *_set 重名。
  总的来说,上面的解决方案可以实现功能,并不影响使用。但我总是觉得这个解决方案有些丑陋。由于不能显式地表达两个模型之间的关系,模型之间的关系看起来不够明了。特别是读代码时,第一个类定义完全没法提示一对多的关系。我必须要看到了第二个类定义,才能搞明白两个模型之间的关系。真希望有一种显式说明关系的办法,降低读代码时的认知负担。
   查询
  Django ORM 可以通过一些方法来实现。其中的很多方法返回的是 Django 自定义的 QuerySet 类的迭代器。 Python 看到迭代器时会懒惰求值,所以这些方法返回时并不会真正进行数据库操作。这样,多个方法串联操作时,就避免了重复操作数据库。返回 QuerySet 的常见方法包括:
   all() filter() exclude() annotate() order_by() reverse() distinct()
  ...
  对于依赖具体数据的操作,QuerySet 会求值。比如遍历 QuerySet 时,就会先执行数据库操作。用 len() 获得 QuerySet 长度时,也会造成 QuerySet 估值。此外 QuerySet 一些方法,比 get() count() earlist() exists() 等,都会对 QuerySet 进行求值。因此,在写程序时,要注意 QuerySet 求值的时间点,避免重复的数据库操作。
  SQL WHERE 条件可以通过参数的形式来传给方法。这些参数一般是 "[ 字段 ]__[ 运算符 ]" 的命名方式,比如:
   Customer.objects.filter(name__contains="abc")
  除了contains ,还有 in gt lt startswith date range 等等操作符,能实现的 WHERE 条件确实够全的了。
  不过,这又是一个有点别扭的地方,即通过命名方式来控制查询行为。我看过有的ORM 是用 lambda 的形式来表达 WHERE 条件,还有的会做一个类似于 contains() 的方法,都要比 Django ORM 的方式好看。如果是跨表查询, Django 的方式就更丑了:
   Customer.objects.filter(company__name__contains="xxx")
  无限的双下划线啊……
   聚合
  Django 实现聚合的方式简直是噩梦。貌似 ORM 对表达 GROUP BY 很无力,源代码里的注释就认输了:
Django ORM模型的一点体会 
聚合的aggregate() annotate() 方法可以实现基本的功能,但稍微复杂一点,代码就变得魔幻了:
Django ORM模型的一点体会 
看到一大串values() annotate() 变来变去,有没有觉得头晕?我觉得这种情况下,可以直接上原始的 SQL 查询语句了,没必要再自己折腾自己。
   F表达式和Q表达式
  F 表达式指代了一列,对于 update 操作时引用列的值有用。 Q 表达式代表了 WHERE 的一个条件,可以用于多个 WHERE 条件的连接。这些都是 Django ORM 用来弥补缺陷的。就拿 Q 表达式来说。查询方法中跟多个参数的话,相当于多个 WHERE 条件。这些条件会默认为 AND 关系。为了表达 OR NOT 关系, Django ORM 就造了个 Q 表达式,比如:
  filter( Q( name__contains="abc")|Q( name__startswith("xxx")))
 为了弥补缺陷,Django ORM 又增加了一种语法风格。于是,学习路上又多了一个坑 ……
   总结
  总的来说,Django ORM 在实现基础的数据库操作方面没问题。但如果需要构建复杂的 SQL 语句,与其在 Django ORM 里绕来绕去,还不如直接用原始的 SQL 语句。这个是我最强烈的一个感受。
来源: 博客园