学习 Entity Framework (第一天):.NET 中 Entity Framework 的数据访问方法






4.96/5 (22投票s)
在本文中,我们将探讨如何利用 Entity Framework 的各种方法,并根据需要加以使用。
目录
引言
本文旨在解释微软 Entity Framework 提供的三种数据访问方法。虽然互联网上已有不少关于这个主题的优秀文章,但我想以更详细的教程形式来涵盖这个主题,为初学 Entity Framework 及其方法的人提供入门指导。我们将一步步探索每种方法,学习如何通过它们在应用程序中使用 Entity Framework 访问数据库和数据。本教程将使用 Entity Framework 6.2 版、.NET Framework 4.6 和 Visual Studio 2017。数据库方面,我们将使用 SQL Server。如果你没有安装 SQL Server,也可以使用 localDB。在本文中,我将解释“数据库优先”和“模型优先”方法,而“代码优先”方法和“代码优先迁移”将在下一篇文章中介绍。
系列信息
我们将遵循一个五篇文章的系列来详细学习 Entity Framework 的主题。所有文章都将是教程形式,最后一篇除外,我将在其中介绍 Entity Framework 的理论、历史和用法。该系列的议题如下。
- 学习 Entity Framework(第 1 天):.NET 中 Entity Framework 的数据访问方法
- 学习 Entity Framework(第 2 天):.NET 中 Entity Framework 的 Code First 迁移
- 学习 Entity Framework(第 3 天):.NET 中 ASP.NET WebAPI 2.0 的 Code First 迁移
- 学习 Entity Framework(
第 4 天): 理解 Entity Framework Core 和 EF Core 中的 Code First Migrations - 学习 Entity Framework(第 5 天):Entity Framework(理论篇)
Entity Framework
微软的 Entity Framework 是一个 ORM(对象关系映射)。维基百科对 ORM 的定义非常直接,并且基本上一目了然。
“对象关系映射(ORM、O/RM 和 O/R 映射工具)在计算机科学中是一种编程技术,用于通过面向对象的编程语言在不兼容的类型系统之间转换数据。这实际上创建了一个‘虚拟对象数据库’,可以在编程语言内部使用。”
作为一个 ORM,Entity Framework 是微软提供的一个数据访问框架,它帮助在应用程序的对象和数据结构之间建立关系。它构建于传统的 ADO.NET 之上,并充当 ADO.NET 的包装器。它是对 ADO.NET 的增强,以更自动化的方式提供数据访问,从而减少了开发人员在处理连接、数据读取器或数据集方面的麻烦。它是对所有这些的抽象,并且功能更强大。开发人员可以更好地控制需要什么数据、以何种形式、以及需要多少。没有数据库开发背景的开发人员可以利用 Entity Framework 和 LINQ 的功能来编写优化的查询以执行数据库操作。SQL 或数据库查询的执行将由 Entity Framework 在后台处理,它会处理所有可能发生的事务和并发问题。你可以参考 EntityFrameworkTutorial 这个网站来深入了解其概念、工作模型、架构等更多内容。
Entity Framework 的方法
了解微软 Entity Framework 提供的三种方法是很常见的。这三种方法如下:
- 模型优先 (Model First)
- 数据库优先
- Code First
- 从数据模型类生成数据库。
- 从现有数据库生成数据模型类。
“模型优先”方法指的是,我们有一个包含各种实体和关系/关联的模型,利用这个模型我们可以生成一个数据库。数据库最终会将实体和属性转换为数据库表和列,将关联和关系分别转换为外键。
“数据库优先”方法指的是,我们已经有了一个现有的数据库,需要在我们的应用程序中访问它。我们可以通过几次点击直接从数据库创建一个实体数据模型及其关系,并开始在代码中访问数据库。所有的实体,例如类,都将由 Entity Framework 生成,这些类可以在应用程序的数据访问层中用于参与数据库操作查询。
“代码优先”方法是 EF 推荐的方法,特别是当你从头开始开发应用程序时。你可以预先定义 POCO 类及其关系,并通过在代码中定义结构来构想数据库结构和数据模型的样貌。最后,Entity Framework 将承担起为你根据 POCO 类和数据模型生成数据库的所有责任,并会处理事务、历史和迁移。
使用这三种方法,你都可以在任何时候根据需要完全控制更新数据库和代码。
模型优先 (Model First)
使用“模型优先”方法,开发人员可能不需要编写任何代码来生成数据库。Entity Framework 提供了设计器工具,可以帮助你创建一个模型,然后据此生成数据库。这些工具更多是拖放控件,只需要输入诸如你的实体名称、它应具有哪些属性、它如何与其他实体关联等信息。用户界面非常易于使用和有趣。模型设计器(准备好后)将帮助你生成 DDL 命令,这些命令可以直接在 Visual Studio 中或在你的数据库服务器上执行,从而根据你创建的模型创建数据库。这会创建一个 EDMX 文件,其中存储了你的概念模型、存储模型以及两者之间映射的信息。要了解概念模型和存储模型,你可以参考我在本文引言中提供的链接。我能看到的唯一缺点是,使用这种方法,完全删除数据库并重新创建它将是一个挑战。
数据库优先 (Database First)
当我们已经有一个现有数据库并需要在应用程序中访问它时,我们会使用“数据库优先”方法。使用 Entity Framework 为现有数据库建立数据访问方法将帮助我们在解决方案中生成上下文和类,通过它们我们可以访问数据库。这与“模型优先”方法相反。在这里,模型是通过数据库创建的,我们可以完全控制选择包含哪些表,以及包含哪些存储过程、函数或视图。你的应用程序可能是一个子应用程序,不需要你庞大数据库中的所有表或对象,所以你可以在这里自由地控制你想要在应用程序中包含什么,不想要什么。每当数据库架构发生变化时,你只需在设计器或实体数据模型中点击一下,就可以轻松更新实体数据模型,它会自动处理映射并在你的应用程序中创建必要的类。
代码优先 (Code First)
使用“代码优先”方法,开发人员的重点只在代码上,而不是数据库或数据模型。开发人员可以在代码中定义类及其映射,并且由于 Entity Framework 现在支持继承,定义关系变得更容易。Entity Framework 负责为你创建或重新创建数据库,不仅如此,在创建数据库时,你还可以提供种子数据。例如,你希望在数据库创建时表中应有的主数据。使用“代码优先”,你可能没有一个包含关系和架构的 .edmx 文件,因为它不依赖于 Entity Framework 设计器及其工具,并且你对数据库有更多的控制权,因为你是创建和管理类与关系的人。后来出现了一个名为“代码优先迁移”的新概念,它使“代码优先”方法更易于使用和遵循,但在本文中,我不会使用迁移,而是使用创建 DB 上下文和 DB 集类的旧方法,以便你理解其底层原理。“代码优先”方法也可以用于从现有数据库生成代码,所以它基本上提供了两种使用方式。
Entity Framework 方法实践
理论说得够多了,让我们开始逐一、逐步地实现,以探索和学习每种方法。我将使用一个示例项目,具体来说是一个控制台应用程序,来通过 Entity Framework 连接数据库,并涵盖所有三种方法。我将使用基本的示例表来解释这个概念。这里的目的是学习概念并实现它,而不是创建一个大型应用程序。当你学会了,你可以将这些概念应用于任何大型企业级应用程序或任何可能拥有数千张表的大型数据库服务器。所以,我们将遵循 KISS 策略,在这里保持简单。
模型优先 (Model First)
- 打开你的 Visual Studio,选择控制台应用程序模板,创建一个简单的 .NET Framework 控制台应用程序。我们可以选择任何应用程序类型,比如 Web 应用程序(ASP.NET Web Forms、MVC 或 Web API)或 Windows 应用程序/WPF 应用程序。你可以为项目和解决方案取一个你喜欢的名字。
- 我们的项目中将只有一个类 *Program.cs* 和一个 *App.config* 文件。
- 右键单击项目并点击“添加新项”,这将打开添加新项的窗口,转到“数据”选项卡,如下图所示,并选择 ADO.NET 实体数据模型。给它取个名字,例如 EFModel,然后点击“添加”。
- 点击“添加”后,你会被要求选择模型内容,在这里你可以选择你想要使用的数据访问方法,即 EF 的三种方法之一。选择“空 EF 设计器模型”,因为我们将使用“模型优先”方法,从头创建一个模型。
- 点击“完成”后,你会看到一个空的设计器窗口,这就是 *.edmx* 文件。解决方案中 *.edmx* 文件的名称就是我们添加 EF 设计器模型时提供的名称。在工具箱中,你可以看到可用于创建实体及其之间关联的工具。
- 从工具箱中将“实体”工具拖放到设计器上。它会创建一个空实体,如下图所示,带有一个名为 Id 的属性,并标明它是主键。在这里,你可以重命名实体并添加更多的标量属性。
- 右键单击创建的实体,并添加一个新的标量属性,如下图所示。将实体名称从 Entity1 重命名为 Student。你可以通过双击实体名称或右键单击并重命名实体来更改名称。
- 将标量属性命名为“Name”。
- 用类似的方法,添加一个名为 `Class` 的新实体,并添加一个名为 `ClassName` 的新属性。我们在这里尝试创建一个学生和班级的关系,即一个班级可以有多个学生。所以,我们可以从工具箱中选择“关联”选项,如下图所示,然后将关联工具从 Class 拖到 Student,它会显示一个一对多的关系。
- 我们不再添加更多实体,而是尝试用这两个实体来理解基本功能。右键单击设计器,然后点击“从模型生成数据库…”选项来生成脚本。
- 点击“从模型生成数据库…”选项后,你会被要求选择一个数据连接,如下图所示。你可以选择一个新连接或一个现有的连接。我将选择一个新连接,但在此之前,我会在我的 SQL 服务器上创建一个空数据库,这样我就不必修改脚本来提供数据库名称。默认情况下,如果未指定数据库名称,生成的脚本会在 master 数据库中创建表。
- 打开你的 SQL Server,创建一个新数据库,并根据你的喜好命名它。我将其命名为 StudentDB,如下图所示。
- 回到需要提供连接细节的窗口。选择你的数据源和服务器名称,如下图所示。服务器名称应该是你创建空数据库的服务器。现在,在选择数据库的选项中,展开下拉菜单,你应该能看到你的数据库名称。选择该数据库名称。
- 一旦你选择了数据库名称,就会生成一个连接字符串,如下所示,并且会提示该连接字符串将以 `EFModelContainer` 的名称保存在 *App.Config* 文件中。`EFModelContainer` 是连接字符串的名称。由于这是一个 EF 生成的连接字符串,你可以看到它也包含了关于我们应用程序中将存在的 EF CSDL、MSL 和 SSDL 文件的信息。点击“下一步”继续。
- 下一步是选择你的 Entity Framework 版本。我们将使用 6.x,即它会自动选择 EF6 的最新稳定版本。点击“下一步”。
- 作为向导的最后一步,你会看到为我们创建的所需 SQL 脚本。你可以选择重命名脚本,但默认情况下,它会取名为 *<模型名称>.edmx.sql*。我将保持原样,并点击“完成”继续。
- 你会在解决方案资源管理器中看到这个脚本。双击打开它,它会在一个窗口中打开,你可以直接执行它。
- 在执行脚本之前,让我们先从 Nuget 包管理器安装 Entity Framework 的最新稳定版本。这很简单。在 Visual Studio 中转到“工具”,然后选择“NuGet 包管理器”->“程序包管理器控制台”,如下图所示。
- NuGet 包管理器控制台窗口默认会在 Visual Studio 底部打开。现在选择需要安装 Entity Framework 包的项目。然后在显示 PM> 的命令中输入 Install-Package EntityFramework 并按回车。我们没有指定 Entity Framework 的版本,因为我们希望下载最新稳定版的包并将其作为 DLL 引用添加到我们的项目中。
- 安装完 Entity Framework 后,回到脚本窗口,在左上角,你会看到执行脚本的按钮,如下所示。按下该按钮来执行脚本。
- 点击“执行”后,会弹出一个新窗口,要求输入服务器和数据库的详细信息。填写特定于你的服务器和数据库的详细信息,如下所示,然后点击“连接”。
- 完成后,转到你的数据库服务器,你会看到我们的 StudentDB 数据库中已经创建了表。表的名称是复数形式,Student 表有一个指向 Classes 表的外键引用,并且外键被自动创建,名为 `Class_Id`,引用 Classes 表。这很神奇,不是吗?
- 在我们的解决方案资源管理器中,我们看到了 *.edmx* 文件以及为 Student 和 Class 实体创建的上下文类和模型类。这一切都是由 EF 设计器在后台完成的。所以,到目前为止,我们没有写一行代码,就得到了 EF 生成的所有代码。
- 打开 *EFModel.Context.cs* 类文件,我们看到生成的 `DbContext` 类的名称是 `EFModelContainer`。请记住,这是我们存储在 *App.Config* 中的连接字符串的名称。上下文类的名称必须与连接字符串的名称相同,EF 才能知道它们之间的关系。所以,你可以在同一个解决方案中有多个不同名称的 DB 上下文类,指向不同的连接字符串。你可以进一步探索 `DbContext` 类以及如何通过其他方式将其与配置文件中的连接字符串关联起来。另一种方法是通过将连接字符串的名称作为参数传递给其参数化构造函数来调用其基类构造函数。但为了便于理解,我们将坚持使用这种实现方式。
- 现在是时候测试我们的实现,看看 Entity Framework 是否真的在工作并帮助我们进行数据库操作了。所以,在 *Program.cs* 类的 `Main` 方法中,我们将尝试编写一些代码,在数据库中为我们保存一个新的班级。创建一个 EFModelContainer 的新对象,在这个容器中,我们可以得到来自 `DbContext` 的实体类集合。添加一个新的 Class。Class 是设计器为我们生成的实体类的名称。并将班级命名为“Nursery”。我们不必为班级指定 id 属性,因为 EF 会自动处理这个问题,并为新添加的记录提供一个 Id。添加一个名为“Nursery”的新班级的代码如下图所示。`container.SaveChanges` 语句在执行时会为我们在数据库中添加一条新记录。
- 只需运行应用程序,让 main 方法的代码执行。完成后,转到你的数据库并检查 Classes 表,你会看到在 Classes 表中添加了一条新记录,班级名称为“Nursery”,这正是我们想要添加记录时提供的内容。所以,它起作用了 😊。注意,Id 是由 Entity Framework 自动生成的。
- 现在,让我们尝试一些新的东西,尝试添加一个新班级,但这次带上学生。我们有一个班级和学生的关系,即一个班级可以有多个学生,一个学生将属于一个班级。如果你想探索这种关系在类中是如何维护的,可以检查为 Student 和 Class 创建的模型类。所以,这一次,我们将添加一个新班级,并向该班级添加一些学生。Entity Framework 应该自动将这些学生添加到 Students 表中,并与 Class 表建立关系。下面是实现这一点的简单且不言自明的代码。
static void Main(string[] args) { EFModelContainer container = new EFModelContainer(); ICollection<Student> students = new List<Student> { new Student() { Name = "Mark" }, new Student() { Name = "Joe" }, new Student() { Name = "Allen" } }; container.Classes.Add(new Class() {ClassName = "KG", Students = students }); container.SaveChanges(); }
在上面的代码中,我们创建了一个 `EFModelContainer` 对象和一个包含三个学生的学生列表。现在,像上一个例子一样,向容器对象添加一个新的班级,并将 `Students` 属性分配给这个学生集合。最后但同样重要的是,调用 `container.SaveChanges()`。
- 运行代码并转到数据库。检查 Classes 表,可以看到一个新创建的班级行,名称为我们从代码中提供的“KG”。
现在,转到 Students 表,我们看到我们从代码中提供的三个学生被创建在那里,并且 Class_Id 列包含了指向新创建的 ID 为 2 的班级的外键引用。太棒了 😊
像这样,你可以通过编写简单的代码在数据库上执行复杂的查询和其他 CRUD 操作。尝试执行更多操作,如编辑、删除、获取记录,以了解更多。让我们转向下一个主题,即使用 Entity Framework 的“数据库优先”方法。
数据库优先 (Database First)
- 就像我们在“模型优先”方法中所做的那样,创建一个新的控制台应用程序,并将其命名为 `EF_DBF`。
- 第二步是向此项目添加一个新的 ADO.NET 实体数据模型。你可以自己命名。我将其命名为 ModelDBF。
- 现在,在选择模型窗口中,我们将选择“来自数据库的 EF 设计器”选项,这将帮助我们从现有数据库创建 Entity Framework 设计器。
- 接下来,为数据库选择连接。例如,在向导中为你的现有数据库提供详细信息。我将使用我们用“模型优先”方法创建的数据库,即 `StudentDB`。一旦我们选择了数据库,我们就会看到 Entity Framework 的连接字符串以及要保存在 App.Config 中的连接字符串的名称,即 `StudentDBEntities`。如果你愿意,也可以更改它。点击“下一步”。
- 选择 EF 版本。我已经解释了 6.x 的含义。我们将选择相同的版本,然后点击“下一步”。
- 现在在这一步,你将看到与你最初选择的数据库相关的所有数据库对象,你可以选择包含或排除你需要的对象。这些对象可以是表、视图或存储过程。由于我们没有视图和存储过程,我们将只选择我们的两个表,如下图所示。由于我们的表名已经是复数形式,我不想通过再次将其复数化并在我的实体类名后附加一个‘s’来使事情复杂化,所以我取消了复数化实体名称的选项。提供模型命名空间或保持默认名称不变,然后点击“完成”。
- 点击“完成”后,你会在 EF 设计器中看到为我们从数据库中选择的数据库对象创建的实体。我们注意到,这与我们手动创建实体并据此生成数据库时的情况很相似。这个 EF 设计器也处理了外键关系,并显示了班级和学生实体之间的一对多关联。
- 是时候像我们在讨论的第一种方法中那样添加 Entity Framework 包了。确保你选择了正确的项目,例如,你需要添加 EF 的当前项目。在程序包管理器控制台中输入命令并按回车。
- 现在,当我们打开生成的 *ModelDBF.Context.cs* 文件时,我们看到部分类的名称是 `StudentDBEntities`,即我们保存在 *App.Config* 中的连接字符串的名称。我已经在上一节中解释了这背后的逻辑。
- 现在是时候看些实际操作了。将下面的代码添加到 *Program.cs* 的 `Main()` 方法中。
static void Main(string[] args) { StudentDBEntities container = new StudentDBEntities(); ICollection<Students> students = new List<Students> { new Students() { Name = "Harry" }, new Students() { Name = "Jane" }, new Students() { Name = "Nick" } }; container.Classes.Add(new Classes() { ClassName = "Class 1", Students = students }); container.SaveChanges(); container.Students.Add(new Students() {Class_Id = 1, Name = "Ben"}); container.SaveChanges(); }
在上面的代码中,我们尝试创建一个 `StudentDBEntities` 类的对象,即我们的上下文类,以及一个要添加到数据库的学生集合。为了检查关系是否正常工作,我们将添加一个名为“Class 1”的新班级,并将 Students 属性分配给我们的学生集合,然后调用 `SaveChanges()`。为了再次检查单个学生插入是否有效,我们将添加一个名为“Ben”的新学生到 Students 模型,并将其班级 ID 分配为 1,即我们数据库中已有的班级,然后调用 `SaveChanges()`。在 Main 方法中设置一个断点,然后按 F5。
- 当应用程序运行时,它会命中该断点。按 F10 逐行执行语句,并停在第 24 行,即在我们添加新学生之前。由于我们已经执行了保存新添加班级的代码,让我们去数据库检查一下。
- 在数据库中,我们看到新添加的班级在 Classes 表中有一行新记录,ID 为 3。
在 Students 表中,我们看到我们从代码中添加的三个学生已经插入到表中,其班级 ID 为 3,即新创建的班级。
- 现在回到 Visual Studio 并执行添加新学生的那一行代码。
完成后,检查数据库,我们看到一个名为“Ben”的新学生已添加到我们的 Students 表中,其 `Class_Id` 为 1,这是我们在代码中分配的。
我们看到我们的“数据库优先”方法也运行良好。同样,你可以随意在代码中尝试其他数据库操作,并多做尝试以探索更多。让我们继续讨论“代码优先”方法。
结论
在本文中,我们仔细研究了如何利用 Entity Framework 的各种方法,并根据需要加以使用。我使用了基本的控制台应用程序来解释这些概念,但它们也可以用于任何使用 Web API、ASP.NET 项目或 MVC 项目的企业级应用程序。我们简要讨论了所用方法的优缺点,并尝试创建了小型示例应用程序来观察它们的运行情况。Entity Framework 中还有很多值得探索的内容,例如,其底层架构是什么,架构如何工作,事务管理,加载方式等。我个人认为 EF 是最好、最强大的 ORM 之一,可以与任何 .NET 应用程序无缝集成。我特意在本文中跳过了“代码优先”方法和“代码优先迁移”,因为这会使文章篇幅过长。在我的**下一篇文章**中,我将解释使用 Entity Framework 的“代码优先”方法以及 Entity Framework 中的“代码优先迁移”。你可以在这里下载关于 Entity Framework 的完整免费电子书(深入了解微软 .NET Entity Framework)。