Frans Bouma 认为代码先行的 ORM 是“愚蠢的”

  • Jonathan Allen
  • 姚琪琳

2014 年 1 月 9 日

话题:.NET语言 & 开发

在使用 ORM 构建基于数据库的项目时,开发者可以选择是先设计数据库表,还是先设计类或抽象模型。为了展开讨论,我们先列出 Frans Bouma 的结论:代码先行的 ORM 是愚蠢的。

先写代码,比如实体类,与先设计表一样有问题,它们都需要反向工程来得到抽象实体定义,以创建“对方”的元素:对类进行反向工程得到抽象实体定义,然后创建表和映射,或对表进行反向工程得到类,然后创建映射,这两者是等价的。核心问题是,如果先设计类或表,就等于先得到了抽象实体定义的某个投影的最终结果:类不是从天上掉下来的,在决定了领域包含这样一个类型后,它就存在了。例如,一个“Customer”,包含给定的字段:Id、CompanyName、Address 等。

他还说道,

我知道“代码先行”的整个思想来源于开发者希望编写代码,用代码来思考,然后将对象持久化到数据库中。但事实是,你持久化到数据库的并不是对象,而是它们的内容,是实体的实例。而一个实体类的实例(即一个对象)则很有可能比实体的实例包含更多的数据,所以看似存储“对象”,实则无法覆盖对象。拿序列化一个对象来打比方,我们序列化的并不是对象,而是其数据的子集,得到的结果与源并不一定匹配。在将数据反序列化为 JavaScript 对象的时候,我们还能将它视为原始的.NET 对象吗?当然不能,它是对象内部的数据,可以存在于任何地方。

那么,当“对象”序列化成 JSON 时,数据被序列化,这没有异议。但当同样的对象序列化成表行时,对象作为一个整体被序列化,这不是更奇怪吗?如果你仍然坚信 ORM 就是持久化对象,那么另一个不使用 ORM 但使用相同数据库的应用程序,在将“对象”持久化为表行时会发生什么呢?这个应用程序(甚至可以用完全不同的语言编写)可以完全正常地读取和消费存储于表行中的实体实例,不需要知道你将其视为一个持久化的.NET 对象。因为让人惊奇的是,表行中的内容并不是持久化的对象,而是持久化的实体实例,一个抽象实体定义的实例,不是类定义的实例。

Reddit 用户 remy_porter 对这个问题有不同的看法

我认为真正糟糕的是 EF 中的模型先行(Model First)。我痛恨它强制你使用的 GUI 工具。

我最喜欢的方式是用代码先行来实现一种殊途同归的方案。我以最有意义的方式编写对象模型,再以最有意义的方式编写数据库模型,然后使用 FluentAPI 来让两者匹配。

不过我承认,我用这种方式只是漫无目的地抛出数据库对象图,因为我告诉过管理层这个应用应该使用 NoSQL,但他们却充耳不闻(该数据模型是存储不同结构的文档,但却要求用 SQL Server)。

Nishruu 倾向于将其用于测试,

没错,我通常使用 Fluent API 来映射已经设计好的数据库。

代码先行真正有用的场景,是用内存 SQLite DB 或某种 LocalDB 来快速进行集成或单元测试。然后可以为测试概括而快速地重新创建数据库结构。

NHibernate 就很好,能和内存 SQLite DB 很好地工作。EF 就不尽如人意了,使用 LocalDB 时只能通过 MDF 文件重新创建数据库架构(schema)。

InfoQ 邀请您阅读Bouma 的完整吐槽,并发表您的观点。

原文英文链接:Frans Bouma Argues Code First O/R Mapping is “Silly”

.NET语言 & 开发