使用实体框架进行对象关系映射
作者:丹尼斯·巴兰特、埃内吉·胡多布雷兹尼克
尽管技术发展迅速,但是管理关系数据库的主要语言仍然是 SQL。一种结构化查询语言,其根源可追溯到 20 世纪 70 年代,有许多改编的衍生产品(PostgreSQL、MySQL 等)。
编写 SQL 查询通常是一项非常耗时的工作,尤其是在数据库结构更复杂的情况下。与面向对象编程语言的典型做法不同,无需手动编写查询和对象转换,而是将类从源代码映射到数据模式,而不是反过来,这样更加自然,而且通常更简单。这节省了开发时间并且使得识别实体之间的关系变得更加容易。这种方法称为对象关系映射(ORM)。
对于 .NET 环境,微软提供了开源的 Entity Framework Core(简称 EF Core),它支持数据驱动开发(所谓的 Code First 方法),其中互连的数据被简单地收集到代表表的类中,框架使用它们来解析关系并以自己的格式创建数据库模式。这种方法的最大优点是无需了解 SQL 就可以在所需的数据库上安装数据模式。
EF Core 通过称为数据库提供程序的插件库与数据库通信,因此迁移到另一个数据库很容易。用户可以通过包管理器(C# 的 NuGet)安装这些。官方库仅适用于 Microsoft 的 SQL Server 和 Azure Cosmos DB 解决方案以及 SQLite 项目,但由于强大的开源社区,该框架几乎支持所有主要关系数据库(MySQL、PostgreSQL 等)。
作为数据建模的一个示例,让我们看一下表“学生”,该表具有属性名字(最多 20 个字符的字符串)、姓氏(最多 20 个字符的字符串)和唯一标识符(整数)。
在PostgreSQL中,我们对该表的定义如下:
除了定义名字和姓氏的基本类型之外,我们还要求它们永远不为空(NOT NULL)。 SERIAL 类型代表一个唯一的整数,在我们的例子中,它充当主键 Id。对于每个添加的实体,都会根据先前添加的身份的ID自动确定,该ID只是简单地递增。
使用 ORM 在应用程序逻辑的源代码中将表定义为类要容易得多,它会自动创建对象和相应的数据库要求。类属性上方的Key标签表示主键,Required表示该属性不能为空。
开发完成后,EF Core 允许我们自动从代码生成数据模式,并通过框架中包含的命令行工具将其部署到选定的数据库。数据库模式的逐步变化是通过所谓的迁移来管理的,这确保数据库与应用程序数据模型保持同步。新的迁移以特殊类的形式存储在 EF Core 中,是通过将当前数据模型与当前数据库模式(上次迁移的状态)进行比较创建的。
数据库连接由继承DbContext类的类抽象。其属性是映射到表的 DbSet 类型的实体的集合。
上述查询编写框架使用 LINQ 语法,它代表了从各种来源检索和处理数据的统一方法。然后将查询转换为 SQL,并将结果本身转换回对象、其属性或对象表。
下面的示例显示了对已知入学编号的学生的查询。只需使用主键值(ID)调用一次 Find 方法就足够了。
如果我们想在不使用 ORM 工具的情况下实现相同的结果,则需要更多的代码。显示了使用 NpgSql 库的示例。首先,我们需要创建代表 SQL 查询和数据库读取的对象,但我们需要小心正确地包含参数以避免潜在的漏洞,例如SQL 注入。这次我们必须自己创建学生对象,但是我们必须小心查询中属性的顺序以及查询中出现错误的可能性。
因此,对象关系映射为我们提供了一个抽象层,由于它映射自面向对象的开发计划,因此可以显著加快软件开发速度,因为 SQL 代码不必与源(通常是面向对象的)代码分开编写。编写良好的 ORM 支持应用程序设计的良好开发模式和实践,同时也允许不熟悉 SQL 的开发人员更轻松地将关系数据库集成到他们的应用程序中。
然而,必须强调的是,ORM 并不是一个完美的解决方案。其弱点恰恰在于它给我们提供的抽象概念。它生成的 SQL 代码比开发人员编写的要多得多,这会严重影响应用程序速度、隐藏不良的数据库访问实践,并且还会造成向后兼容性问题。虽然生成的代码在大多数情况下是正确的,但仍然建议手动检查和测试它。
对象关系映射不是 .NET 环境原生的功能。大多数针对不同语言的框架和库都提供该功能。例如 Python 的 Django、Go 的 Gorm、Java 的 Spring、JavaScript(或 Node.js)的 Prisma……