张高兴的 Entity Framework Core 即学即用:(一)创建第一个 EF Core 应用
写在前面
Entity Framework Core (EF Core) 是 .NET 平台流行的对象关系映射(ORM)框架。虽然 .NET 平台中 ORM 框架有很多,比如 Dapper、NHibernate、PetaPoco 等,并且 EF Core 的性能也不是最优的(这是由于 EF 的实体跟踪特性,将其禁用后可以大幅提升性能),但依然吸引到很多后端开发者的使用,原因如下:
- EF Core 由 .NET 官方进行开发维护,出现问题解决较为及时,这是很多国产 ORM 框架不具有的优势;
- EF Core 和 C# 语法高度绑定,使用 LINQ 不再需要编写复杂的数据库访问代码;
- EF Core 支持大部分流行的数据库,切换数据库时只需要更改数据库访问驱动,并不需要更改业务逻辑。
因此在项目中使用 EF Core 不一定是最优的,但一定不会错。
《张高兴的 Entity Framework Core 即学即用》系列博客将会从实践的角度去介绍 EF Core。由于学习的是数据库访问技术,因此还需要一个数据库供我们实践。将根据如下背景设计一个数据库,本系列博客将基于此数据库进行实践:
新冠肺炎的流行打破了人们原有的正常生活。为了更好地预防和控制疫情,我们决定开发一个病毒检测管理系统,用于记录公民的核酸检测报告。核酸检测在医院进行,所有检测报告将由病毒检测管理系统收集统计。当前有多家医院可以进行核酸检测,未来这些医院的数量也会增加。考虑到病毒的变异以及未来的扩展性,病毒检测管理系统还需要支持存储不同病毒的检测报告。核酸检测的流程由收集患者的基本信息开始,然后是收集样本的类型,最后出具带有医生姓名的核酸检测报告。
《张高兴的 Entity Framework Core 即学即用》系列博客使用 .NET 6 和 EF Core 6 进行编码,保证了技术的时效性。和绝大部分 EF Core 的教程不同,这里并没有选择使用 SQL Server,而是使用 PostgreSQL 数据库。此处并没有否认 SQL Server 是一款优秀的数据库,并且 EF 的很多特性在 SQL Server 上表现更好,可以说 SQL Server 是 EF Core 的最佳实践。但 SQL Server 最致命的问题是闭源并且收费,现在虽然可以跨平台运行,但这个方向仍有很长的路要走。PostgreSQL 开源且免费,甚至可以运行在 ARM 的 Linux 开发板上,性能也要优于 MySQL。PostgreSQL 扩展性高,拥有庞大的插件群,并且还具有一些“领先时代”的功能,可以说是数据库界的 C#。当然本系列博客并没有涉及到数据库的原生操作,如果你不想使用 PostgreSQL,可以直接将 NuGet 包替换成对应数据库的即可,这也是 EF Core 的优势。
《张高兴的 Entity Framework Core 即学即用》系列博客共分为 4 个部分:
- 第一部分将从 0 开始创建一个 EF Core 应用,介绍了使用 Database First 的方式以及手工的方式生成实体类,并且尝试查询一张表的数据;
- 第二部分介绍了 EF Core 的实体状态以及增删改查等数据库操作;
- 第三部分实现了一个 EF Core 的帮助类,以简化数据库的操作和增强扩展性;
- 第四部分使用 Razor 引擎实现了一个实体类生成工具。
每一篇博客在介绍功能点时都附带有简单的示例,每一章的最后还附有若干个小练习,希望读者可以借着练习帮助理解,之后根据项目中遇到的问题再学习其他的内容。欢迎批评与指正,有任何的问题都可以通过邮件或者评论的方式与我交流。
张高兴
2022年3月22日
本文将使用 .NET 6 创建一个控制台程序,从 0 开始,学习 EF Core 的使用。通过本文你可以学到:
- 使用 Database First 的方式生成实体类;
- 熟悉实体类中的 EF Core Attribute;
- 查询一张表的数据;
- 使用
Docker拉取镜像。
准备工作
准备工作包含两部分:安装数据库与创建数据库。EF Core 对 PostgreSQL 的版本没有要求,但后续的博客在介绍编写实体类生成工具时要求 12 及以上的版本。
安装 PostgreSQL
直接安装
PostgreSQL 支持在绝大多数操作系统下运行,下载地址:https://www.postgresql.org/download
Windows 下载 exe 安装包,安装时直接点击“下一步”即可,无需额外配置。如果使用 Debian 系列的 Linux 发行版时,直接使用 apt 进行安装,也无需其他操作。其他的操作系统建议根据下载地址中的安装指南进行操作。
使用 Docker 拉取镜像
- 拉取 PostgreSQL 镜像:
docker pull postgres
- 创建卷,用于持久化数据库数据:
docker volume create pgsql_data
- 运行镜像,端口映射为
54321,密码配置为弱密码@Passw0rd:
docker run -d --name pgsql -p 54321:5432 --restart=always -e POSTGRES_PASSWORD='@Passw0rd' -e TZ='Asia/Shanghai' -e ALLOW_IP_RANGE=0.0.0.0/0 -v pgsql_data:/var/lib/postgresql postgres
数据库的表结构
数据库的设计取决于业务的需求,对同样的业务,每个人的设计都有可能不同,数据库的设计并没有标准答案,读者们或许有更好的设计方案,这里给出的表结构仅供参考,只是为了满足教程的需要:

🛈 重要
下面使用熟悉的数据库管理工具,如 pgAdmin、Navicat 等,创建数据库 pandemic,具体的执行 SQL 如下,删减了字段注释等不必要的语句:
create table doctor (
id SERIAL not null,
name VARCHAR(20) not null,
hospital_id INT4 not null,
is_deleted INT4 not null default 0,
creator_id VARCHAR(50) null,
created_dt TIMESTAMP not null default 'now()',
modifier_id VARCHAR(50) null,
modified_dt TIMESTAMP not null default 'now()',
constraint PK_DOCTOR primary key (id)
);
create table hospital (
id SERIAL not null,
name VARCHAR(20) not null,
is_deleted INT4 not null default 0,
creator_id VARCHAR(50) null,
created_dt TIMESTAMP not null default 'now()',
modifier_id VARCHAR(50) null,
modified_dt TIMESTAMP not null default 'now()',
constraint PK_HOSPITAL primary key (id)
);
create table patient (
id SERIAL not null,
name VARCHAR(20) not null,
sex VARCHAR(10) null,
age INT2 null,
mobile VARCHAR(15) null,
is_deleted INT4 not null default 0,
creator_id VARCHAR(50) null,
created_dt TIMESTAMP not null default 'now()',
modifier_id VARCHAR(50) null,
modified_dt TIMESTAMP not null default 'now()',
constraint PK_PATIENT primary key (id)
);
create table report (
id SERIAL not null,
report_type_cd VARCHAR(20) not null,
doctor_id INT4 not null,
patient_id INT4 not null,
result BOOL not null default FALSE,
collect_time TIMESTAMP null,
test_time TIMESTAMP null,
report_time TIMESTAMP null,
description VARCHAR(200) null,
is_deleted INT4 not null default 0,
creator_id VARCHAR(50) null,
created_dt TIMESTAMP not null default 'now()',
modifier_id VARCHAR(50) null,
modified_dt TIMESTAMP not null default 'now()',
constraint PK_REPORT primary key (id)
);
create table report_type (
cd VARCHAR(20) not null,
name VARCHAR(20) null,
description VARCHAR(200) null default 'covid',
is_deleted INT4 not null default 0,
creator_id VARCHAR(50) null,
created_dt TIMESTAMP not null default 'now()',
modifier_id VARCHAR(50) null,
modified_dt TIMESTAMP not null default 'now()',
constraint PK_REPORT_TYPE primary key (cd)
);
alter table doctor
add constraint FK_DOCTOR_REFERENCE_HOSPITAL foreign key (hospital_id)
references hospital (id)
on delete restrict on update restrict;
alter table report
add constraint FK_REPORT_REFERENCE_DOCTOR foreign key (doctor_id)
references doctor (id)
on delete restrict on update restrict;
alter table report
add constraint FK_REPORT_REFERENCE_REPORT_T foreign key (report_type_cd)
references report_type (cd)
on delete restrict on update restrict;
alter table report
add constraint FK_REPORT_REFERENCE_PATIENT foreign key (patient_id)
references patient (id)
on delete restrict on update restrict;
INSERT INTO report_type (cd, name) VALUES ('COVID-1', '新冠肺炎1号');
INSERT INTO report_type (cd, name) VALUES ('COVID-2', '新冠肺炎2号');
INSERT INTO report_type (cd, name) VALUES ('COVID-3', '新冠肺炎3号');
INSERT INTO report_type (cd, name) VALUES ('COVID-4', '新冠肺炎4号');
INSERT