12 Saving data with Entity Framework Core
12 使用 Entity Framework Core 保存数据
This chapter covers
本章涵盖
-
Understanding what Entity Framework Core is and why you should use it
了解什么是 Entity Framework Core 以及为什么应该使用它 -
Adding Entity Framework Core to an ASP.NET Core application
将 Entity Framework Core 添加到 ASP.NET Core 应用程序 -
Building a data model and using it to create a database
构建数据模型并使用它来创建数据库 -
Querying, creating, and updating data with Entity Framework Core
使用 Entity Framework Core 查询、创建和更新数据
Most applications that you’ll build with ASP.NET Core require storing and loading some kind of data. Even the examples so far in this book have assumed that you have some sort of data store—storing exchange rates, user shopping carts, or the locations of physical stores. I’ve glossed over this topic for the most part, but typically you’ll store this data in a database.
您将使用 ASP.NET Core 构建的大多数应用程序都需要存储和加载某种类型的数据。即使是本书中到目前为止的示例也假定您有某种数据存储 — 存储汇率、用户购物车或实体商店的位置。我大部分时间都忽略了这个主题,但通常您会将此数据存储在数据库中。
Working with databases can be a rather cumbersome process. You have to manage connections to the database, translate data from your application to a format the database can understand, and handle a plethora of other subtle problems. You can manage this complexity in a variety of ways, but I’m going to focus on using a library built for modern .NET: Entity Framework Core (EF Core). EF Core is a library that lets you quickly and easily build database access code for your ASP.NET Core applications. It’s modeled on the popular Entity Framework 6.x library, but it has significant changes that make it stand alone in its own right as more than an upgrade.
使用数据库可能是一个相当繁琐的过程。您必须管理与数据库的连接,将应用程序中的数据转换为数据库可以理解的格式,并处理大量其他细微的问题。您可以通过多种方式管理这种复杂性,但我将重点介绍如何使用为现代 .NET 构建的库:Entity Framework Core (EF Core)。EF Core 是一个库,可让您快速轻松地为 ASP.NET Core 应用程序构建数据库访问代码。它以流行的 Entity Framework 6.x 库为模型,但它具有重大变化,使其本身不仅仅是升级。
The aim of this chapter is to provide a quick overview of EF Core and show how you can use it in your applications to query and save to a database quickly. You’ll learn enough to connect your app to a database and manage schema changes to the database, but I won’t be going into great depth on any topics.
本章旨在提供 EF Core 的快速概述,并演示如何在应用程序中使用它来快速查询和保存到数据库。您将学到足够的知识来将应用程序连接到数据库并管理数据库的架构更改,但我不会深入讨论任何主题。
Note For an in-depth look at EF Core, I recommend Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021). Alternatively, you can read about EF Core on the Microsoft documentation website at https://docs.microsoft.com/ef/core.
注意 要深入了解 EF Core,我推荐 Jon P. Smith 的 Entity Framework Core in Action,第 2 版(Manning,2021 年)。或者,您可以在 https://docs.microsoft.com/ef/core Microsoft 文档网站上阅读有关 EF Core 的信息。
Section 12.1 introduces EF Core and explains why you may want to use it in your applications. You’ll learn how the design of EF Core helps you iterate quickly on your database structure and reduce the friction of interacting with a database.
第 12.1 节介绍了 EF Core,并解释了为什么你可能想要在应用程序中使用它。你将了解 EF Core 的设计如何帮助你快速迭代数据库结构并减少与数据库交互的摩擦。
In section 12.2 you’ll learn how to add EF Core to an ASP.NET Core app and configure it by using the ASP.NET Core configuration system. You’ll see how to build a model for your app that represents the data you’ll store in the database and how to hook it into the ASP.NET Core DI container.
在第 12.2 节中,你将了解如何将 EF Core 添加到 ASP.NET Core 应用,并使用 ASP.NET Core 配置系统对其进行配置。您将了解如何为应用程序构建一个模型,该模型表示您将存储在数据库中的数据,以及如何将其挂接到 ASP.NET Core DI 容器中。
Note For this chapter I use SQLite, a small, fast, cross-platform database engine, but none of the code shown in this chapter is specific to SQLite. The code sample for the book also includes a version using SQL Server Express’s LocalDB feature. This version is installed as part of Visual Studio 2022 (when you choose the ASP.NET and Web Development workload), and it provides a lightweight SQL Server engine. You can read more about LocalDB at http://mng.bz/5jEa.
注意 在本章中,我使用了 SQLite,这是一个小型、快速、跨平台的数据库引擎,但本章中展示的代码都不是特定于 SQLite 的。该书的代码示例还包括使用 SQL Server Express 的 LocalDB 功能的版本。此版本作为 Visual Studio 2022 的一部分安装(当您选择 ASP.NET 和 Web 开发工作负载时),它提供轻量级 SQL Server 引擎。您可以在 http://mng.bz/5jEa 上阅读有关 LocalDB 的更多信息。
No matter how carefully you design your original data model, the time will come when you need to change it. In section 12.3 I show how you can easily update your model and apply these changes to the database itself, using EF Core for all the heavy lifting.
无论您多么仔细地设计原始数据模型,都需要更改它。在部分12.3 我将展示如何轻松更新模型并将这些更改应用于数据库本身,从而使用 EF Core 完成所有繁重的工作。
When you have EF Core configured and a database created, section 12.4 shows how to use it in your application code. You’ll see how to create, read, update, and delete (CRUD) records, and you’ll learn about some of the patterns to use when designing your data access.
配置 EF Core 并创建数据库后,第 12.4 节介绍了如何在应用程序代码中使用它。您将了解如何创建、读取、更新和删除 (CRUD) 记录,并了解在设计数据访问时要使用的一些模式。
In section 12.5 I highlight a few of the problems you’ll want to take into consideration when using EF Core in a production app. A single chapter on EF Core can offer only a brief introduction to all the related concepts, so if you choose to use EF Core in your own applications—especially if you’re using such a data access library for the first time—I strongly recommend reading more after you have the basics from this chapter.
在第 12.5 节中,我重点介绍了在生产应用程序中使用 EF Core 时需要考虑的一些问题。EF Core 的一章只能简要介绍所有相关概念,因此,如果您选择在自己的应用程序中使用 EF Core(尤其是您是第一次使用此类数据访问库),我强烈建议您在了解本章的基础知识后再阅读更多内容。
Before we get into any code, let’s look at what EF Core is, what problems it solves, and when you may want to use it.
在开始任何代码之前,让我们看看 EF Core 是什么,它解决了什么问题,以及何时可能需要使用它。
12.1 Introducing Entity Framework Core
12.1 Entity Framework Core 简介
Database access code is ubiquitous across web applications. Whether you’re building an e-commerce app, a blog, or the Next Big Thing™, chances are that you’ll need to interact with a database.
数据库访问代码在 Web 应用程序中无处不在。无论您是构建电子商务应用程序、博客还是 Next Big Thing™,您都有可能需要与数据库进行交互。
Unfortunately, interacting with databases from app code is often a messy affair, and you can take many approaches. A task as simple as reading data from a database, for example, requires handling network connections, writing SQL statements, and handling variable result data. The .NET ecosystem has a whole array of libraries you can use for this task, ranging from the low-level ADO.NET libraries to higher-level abstractions such as EF Core.
遗憾的是,从应用程序代码与数据库交互通常是一件麻烦的事情,您可以采用多种方法。例如,像从数据库读取数据这样简单的任务需要处理网络连接、编写 SQL 语句和处理可变结果数据。.NET 生态系统具有可用于此任务的一整套库,从低级 ADO.NET 库到高级抽象(如 EF Core)。
In this section, I describe what EF Core is and the problem it’s designed to solve. I cover the motivation for using an abstraction such as EF Core and how it helps bridge the gap between your app code and your database. As part of that discussion, I present some of the tradeoffs you’ll make by using EF Core in your apps, which should help you decide whether it’s right for your purposes. Finally, we’ll take a look at an example EF Core mapping, from app code to database, to get a feel for EF Core’s main concepts.
在本节中,我将介绍什么是 EF Core 以及它旨在解决的问题。我将介绍使用 EF Core 等抽象的动机,以及它如何帮助弥合应用程序代码和数据库之间的差距。作为该讨论的一部分,我将介绍在应用程序中使用 EF Core 将做出的一些权衡,这应该有助于您确定它是否适合您的目的。最后,我们将查看从应用代码到数据库的 EF Core 映射示例,以了解 EF Core 的主要概念。
12.1.1 What is EF Core?
12.1.1 什么是 EF Core?
EF Core is a library that provides an object-oriented way to access databases. It acts as an object-relational mapper (ORM), communicating with the database for you and mapping database responses to .NET classes and objects, as shown in figure 12.1.
EF Core 是一个库,它提供了一种面向对象的方式来访问数据库。它充当对象关系映射器 (ORM),为您与数据库通信,并将数据库响应映射到 .NET 类和对象,如图 12.1 所示。
Figure 12.1 EF Core maps .NET classes and objects to database concepts such as tables and rows.
图 12.1 EF Core 将 .NET 类和对象映射到数据库概念,例如表和行。
Definition With an object-relational mapper (ORM), you can manipulate a database with object-oriented concepts such as classes and objects by mapping them to database concepts such as tables and columns.
定义 使用对象关系映射器 (ORM),您可以使用面向对象的概念来作数据库,例如类和对象,方法是将它们映射到表和列等数据库概念。
EF Core is based on but distinct from the existing Entity Framework libraries (currently up to version 6.x). It was built as part of the .NET Core push to work cross-platform, but with additional goals in mind. In particular, the EF Core team wanted to make a highly performant library that could be used with a wide range of databases.
EF Core 基于现有的实体框架库(当前最高版本 6.x),但不同于现有实体框架库。它是作为 .NET Core 跨平台推动工作的一部分构建的,但考虑了其他目标。具体而言,EF Core 团队希望制作一个可与各种数据库一起使用的高性能库。
There are many types of databases, but probably the most commonly used family is relational databases, accessed via Structured Query Language (SQL). This is the bread and butter of EF Core; it can map Microsoft SQL Server, SQLite, MySQL, Postgres, and many other relational databases. It even has a cool in-memory feature you can use when testing to create a temporary database. EF Core uses a provider model, so support for other relational databases can be plugged in later as they become available.
数据库有很多类型,但最常用的系列可能是关系数据库,通过结构化查询语言 (SQL) 访问。这是 EF Core 的生计;它可以映射 Microsoft SQL Server、SQLite、MySQL、Postgres 和许多其他关系数据库。它甚至还有一个很酷的内存功能,您可以在测试创建临时数据库时使用。EF Core 使用提供程序模型,因此可以在其他关系数据库可用时插入对它们的支持。
Note As of .NET Core 3.0, EF Core also works with nonrelational, NoSQL, or document databases like Cosmos DB too. I’m going to consider mapping only to relational databases in this book, however, as that’s the most common requirement in my experience. Historically, most data access, especially in the .NET ecosystem, has used relational databases, so it generally remains the most popular approach.
注意 从 .NET Core 3.0 开始,EF Core 也适用于非关系数据库、NoSQL 数据库或 Cosmos DB 等文档数据库。但是,在本书中,我将只考虑映射到关系数据库,因为这是我经验中最常见的要求。从历史上看,大多数数据访问(尤其是在 .NET 生态系统中)都使用关系数据库,因此它通常仍然是最流行的方法。
That discussion covers what EF Core is but doesn’t dig into why you’d want to use it. Why not access the database directly by using the traditional ADO.NET libraries? Most of the arguments for using EF Core can be applied to ORMs in general, so what are the advantages of an ORM?
该讨论涵盖了 EF Core 是什么,但并未深入探讨你为何要使用它。为什么不使用传统的 ADO.NET 库直接访问数据库呢?大部分使用 EF Core 的论点通常可以应用于 ORM,那么 ORM 有哪些优势呢?
12.1.2 Why use an object-relational mapper?
12.1.2 为什么使用对象关系映射器?
One of the biggest advantages of an ORM is the speed with which it allows you to develop an application. You can stay in the familiar territory of object-oriented .NET, often without needing to manipulate a database directly or write custom SQL.
ORM 的最大优势之一是它允许您开发应用程序的速度。您可以停留在熟悉的面向对象的 .NET 领域,通常不需要直接作数据库或编写自定义 SQL。
Suppose that you have an e-commerce site, and you want to load the details of a product from the database. Using low-level database access code, you’d have to open a connection to the database; write the necessary SQL with the correct table and column names; read the data over the connection; create a plain old CLR object (POCO) to hold the data; and set the properties on the object, converting the data to the correct format manually as you go. Sounds painful, right?
假设您有一个电子商务网站,并且您希望从数据库中加载产品的详细信息。使用低级数据库访问代码,您必须打开与数据库的连接;使用正确的表和列名称编写必要的 SQL;通过连接读取数据;创建一个普通的旧 CLR 对象 (POCO) 来保存数据;并设置对象的属性,并随时手动将数据转换为正确的格式。听起来很痛苦,对吧?
An ORM such as EF Core takes care of most of this work for you. It handles the connection to the database, generates the SQL, and maps data back to your POCO objects. All you need to provide is a LINQ query describing the data you want to retrieve.
EF Core 等 ORM 会为你处理大部分工作。它处理与数据库的连接,生成 SQL,并将数据映射回 POCO 对象。您只需提供一个 LINQ 查询,描述您要检索的数据。
ORMs serve as high-level abstractions over databases, so they can significantly reduce the amount of plumbing code you need to write to interact with a database. At the most basic level, they take care of mapping SQL statements to objects, and vice versa, but most ORMs take this process a step further and provide additional features.
ORM 充当数据库的高级抽象,因此它们可以显著减少与数据库交互所需编写的管道代码量。在最基本的层面上,它们负责将 SQL 语句映射到对象,反之亦然,但大多数 ORM 将此过程更进一步并提供了额外的功能。
ORMs like EF Core keep track of which properties have changed on any objects they retrieve from the database, which lets you load an object from the database by mapping it from a database table, modify it in .NET code, and then ask the ORM to update the associated record in the database. The ORM works out which properties have changed and issues update statements for the appropriate columns, saving you a bunch of work.
EF Core 等 ORM 会跟踪它们从数据库中检索的任何对象上的哪些属性已更改,这允许您通过从数据库表映射对象、在 .NET 代码中修改对象,然后要求 ORM 更新数据库中的关联记录来加载数据库对象。ORM 会找出哪些属性已更改,并为相应的列发出 update 语句,从而为您节省大量工作。
As is so often the case in software development, using an ORM has its drawbacks. One of the biggest advantages of ORMs is also their Achilles’ heel: they hide the database from you. Sometimes this high level of abstraction can lead to problematic database query patterns in your apps. A classic example is the N+1 problem, in which what should be a single database request turns into separate requests for every single row in a database table.
与软件开发中经常出现的情况一样,使用 ORM 也有其缺点。ORM 的最大优势之一也是它们的致命弱点:它们对您隐藏了数据库。有时,这种高级别的抽象可能会导致您的应用程序中出现有问题的数据库查询模式。一个典型的例子是 N+1 问题,在这个问题中,本应是单个数据库请求的内容变成了对数据库表中每一行的单独请求。
Another commonly cited drawback is performance. ORMs are abstractions over several concepts, so they inherently do more work than if you were to handcraft every piece of data access in your app. Most ORMs, EF Core included, trade some degree of performance for ease of development.
另一个经常被提及的缺点是性能。ORM 是多个概念的抽象,因此它们本质上比您手动创建应用程序中的每一条数据访问要多。大多数 ORM(包括 EF Core)都以某种程度的性能为代价来简化开发。
That said, if you’re aware of the pitfalls of ORMs, you can often drastically simplify the code required to interact with a database. As with anything, if the abstraction works for you, use it; otherwise, don’t. If you have only minimal database access requirements or need the best performance you can get, an ORM such as EF Core may not be the right fit.
也就是说,如果您意识到 ORM 的陷阱,您通常可以大大简化与数据库交互所需的代码。与任何事情一样,如果抽象对您有用,请使用它;否则,不要。如果您只有最小的数据库访问要求或需要您可以获得的最佳性能,则 EF Core 等 ORM 可能不适合。
An alternative is to get the best of both worlds: use an ORM for the quick development of the bulk of your application, and fall back to lower-level APIs such as ADO.NET for those few areas that prove to be bottlenecks. That way, you can get good-enough performance with EF Core, trading performance for development time, and optimize only those areas that need it.
另一种选择是两全其美:使用 ORM 快速开发大部分应用程序,并回退到较低级别的 API,例如 ADO.NET 用于那些被证明是瓶颈的少数领域。这样,您就可以使用 EF Core 获得足够好的性能,以牺牲开发时间来换取性能,并仅优化需要它的领域。
Note These days, the performance aspect is one of the weaker arguments against ORMs. EF Core uses many database tricks and crafts clean SQL queries, so unless you’re a database expert, you may find that it outperforms even your handcrafted ADO.NET queries!
注意 如今,性能方面是反对 ORM 的较弱的论点之一。EF Core 使用许多数据库技巧并制作干净的 SQL 查询,因此,除非你是数据库专家,否则你可能会发现它甚至优于你手工制作的 ADO.NET 查询!
Even if you decide to use an ORM in your app, many ORMs are available for .NET, of which EF Core is one. Whether EF Core is right for you depends on the features you need and the tradeoffs you’re willing to make to get them. Section 12.1.3 compares EF Core with Microsoft’s other offering, Entity Framework, but you could consider many other alternatives, such as Dapper and NHibernate, each of which has its own set of tradeoffs.
即使您决定在应用程序中使用 ORM,也有许多 ORM 可用于 .NET,EF Core 就是其中之一。EF Core 是否适合你取决于你需要的功能以及你愿意为获得这些功能而做出的权衡。12.1.3部分将 EF Core 与 Microsoft 的其他产品 Entity Framework 进行比较,但您可以考虑许多其他替代方案,例如 Dapper 和 NHibernate,每个替代方案都有自己的一组权衡。
12.1.3 When should you choose EF Core?
12.1.3 何时应选择 EF Core?
Microsoft designed EF Core as a reimagining of the mature Entity Framework 6.x (EF 6.x) ORM, which it released in 2008. With many years of development behind it, EF 6.x was a stable and feature-rich ORM, but it’s no longer under active development.
Microsoft 将 EF Core 设计为对成熟的 Entity Framework 6.x (EF 6.x) ORM 的重新构想,该 ORM 在2008. 经过多年的开发,EF 6.x 是一个稳定且功能丰富的 ORM,但它不再处于积极开发阶段。
EF Core, released in 2016, is a comparatively new project. The APIs of EF Core are designed to be close to those of EF 6.x—though they aren’t identical—but the core components have been completely rewritten. You should consider EF Core to be distinct from EF 6.x; upgrading directly from EF 6.x to EF Core is nontrivial.
EF Core 于 2016 年发布,是一个相对较新的项目。EF Core 的 API 设计为接近 EF 6.x 的 API(尽管它们并不相同),但核心组件已完全重写。您应该将 EF Core 与 EF 6.x 区分开来;直接从 EF 6.x 升级到 EF Core 并非易事。
Although Microsoft supports both EF Core and EF 6.x, EF 6.x isn’t recommended for new .NET applications. There’s little reason to start a new application with EF 6.x these days, but the exact tradeoffs will depend largely on your specific app. If you decide to choose EF 6.x instead of EF Core, make sure that you understand what you’re sacrificing. Also make sure that you keep an eye on the guidance and feature comparison from the EF team at http://mng.bz/GxgA.
尽管 Microsoft 同时支持 EF Core 和 EF 6.x,但不建议将 EF 6.x 用于新的 .NET 应用程序。如今,几乎没有理由使用 EF 6.x 启动新应用程序,但确切的权衡在很大程度上取决于您的特定应用程序。如果您决定选择 EF 6.x 而不是 EF Core,请确保您了解您正在牺牲什么。此外,请务必密切关注 http://mng.bz/GxgA 的 EF 团队提供的指南和功能比较。
If you decide to use an ORM for your app, EF Core is a great contender. It’s also supported out of the box by various other subsystems of ASP.NET Core. In chapter 23 you’ll see how to use EF Core with the ASP.NET Core Identity authentication system for managing users in your apps.
如果你决定为应用使用 ORM,EF Core 是一个很好的竞争者。ASP.NET Core 的各种其他子系统也支持开箱即用。在第 23 章中,你将了解如何将 EF Core 与 ASP.NET Core Identity 身份验证系统配合使用,以管理应用中的用户。
Before I get into the nitty-gritty of using EF Core in your app, I’ll describe the application we’re going to be using as the case study for this chapter. I’ll go over the application and database details and discuss how to use EF Core to communicate between the two.
在深入探讨在您的应用程序中使用 EF Core 的细节之前,我将介绍我们将用作本章案例研究的应用程序。我将介绍应用程序和数据库的详细信息,并讨论如何使用 EF Core 在两者之间进行通信。
12.1.4 Mapping a database to your application code
12.1.4 将数据库映射到应用程序代码
EF Core focuses on the communication between an application and a database, so to show it off, you need an application. This chapter uses the example of a simple cooking app API that lists recipes and lets you retrieve a recipe’s ingredients, as shown in figure 12.2. Users can list all recipes, add new ones, edit recipes, and delete old ones.
EF Core 侧重于应用程序和数据库之间的通信,因此要展示它,您需要一个应用程序。本章使用一个简单的烹饪应用程序 API 示例,该 API 列出了食谱并允许您检索食谱的成分,如图 12.2 所示。用户可以列出所有配方、添加新配方、编辑配方和删除旧配方。
Figure 12.2 The recipe app provides an API for managing recipes. You can view, update, and delete recipes, as well as create new ones.
图 12.2 配方应用程序提供用于管理配方的 API。您可以查看、更新和删除食谱,以及创建新的食谱。
This API is obviously a simple one, but it contains all the database interactions you need with its two entities: Recipe and Ingredient.
这个 API 显然很简单,但它包含了您需要的所有数据库交互,它与两个实体:Recipe 和 Ingredient。
Definition An entity is a .NET class that’s mapped by EF Core to the database. These are classes you define, typically as POCO classes, that can be saved and loaded by mapping to database tables using EF Core.
定义 实体是由 EF Core 映射到数据库的 .NET 类。这些是你定义的类,通常为 POCO 类,可以通过使用 EF Core 映射到数据库表来保存和加载这些类。
When you interact with EF Core, you’ll be using primarily POCO entities and a database context that inherits from the DbContext EF Core class. The entity classes are the object-oriented representations of the tables in your database; they represent the data you want to store in the database. You use the DbContext in your application both to configure EF Core and access the database at runtime.
与 EF Core 交互时,将主要使用 POCO 实体和从 DbContext EF Core 类继承的数据库上下文。实体类是数据库中表的面向对象的表示形式;它们表示要存储在数据库中的数据。您可以在应用程序中使用 DbContext 来配置 EF Core 并在运行时访问数据库。
Note You can potentially have multiple DbContexts in your application and even configure them to integrate with different databases.
注意 您的应用程序中可能有多个 DbContext,甚至可以将它们配置为与不同的数据库集成。
When your application first uses EF Core, EF Core creates an internal representation of the database based on the DbSet
当应用程序首次使用 EF Core 时,EF Core 会根据应用程序的 DbContext 和实体类本身的 DbSet
Figure 12.3 EF Core creates an internal model of your application’s data model by exploring the types in your code. It adds all the types referenced in the DbSet<> properties on your app’s DbContext and any linked types.
图 12.3 EF Core 通过浏览代码中的类型来创建应用程序数据模型的内部模型。它会添加DbSet<> 应用的 DbContext 属性和任何链接类型。
For the recipe app, EF Core builds a model of the Recipe class because it’s exposed on the AppDbContext as a DbSet
对于配方应用,EF Core 会生成 Recipe 类的模型,因为它在 AppDbContext 上作为 DbSet
EF Core maps each entity to a table in the database, but it also maps the relationships between the entities. Each recipe can have many ingredients, but each ingredient (which has a name, quantity, and unit) belongs to one recipe, so this is a many-to-one relationship. EF Core uses that knowledge to correctly model the equivalent many-to-one database structure.
EF Core 将每个实体映射到数据库中的表,但它也会映射实体之间的关系。每个配方可以包含许多成分,但每个成分(具有名称、数量和单位)都属于一个配方,因此这是一种多对一关系。EF Core 使用该知识对等效的多对一数据库结构进行正确建模。
Note Two different recipes, such as fish pie and lemon chicken, may use an ingredient that has both the same name and quantity, such as the juice of one lemon, but they’re fundamentally two different instances. If you update the lemon chicken recipe to use two lemons, you wouldn’t want this change to automatically update the fish pie to use two lemons too!
注意 两种不同的食谱,例如鱼馅饼和柠檬鸡,可能使用名称和数量相同的成分,例如一个柠檬的汁液,但它们本质上是两个不同的实例。如果您将柠檬鸡食谱更新为使用两个柠檬,您肯定不希望此更改自动更新鱼馅饼食谱以使用两个柠檬!
EF Core uses the internal model it builds when interacting with the database to ensure that it builds the correct SQL to create, read, update, and delete entities.
EF Core 在与数据库交互时使用它生成的内部模型,以确保它生成正确的 SQL 来创建、读取、更新和删除实体。
Right—it’s about time for some code! In section 12.2, you’ll start building the recipe app. You’ll see how to add EF Core to an ASP.NET Core application, configure a database provider, and design your application’s data model.
好了 — 是时候编写一些代码了!在第 12.2 节中,您将开始构建配方应用程序。你将了解如何将 EF Core 添加到 ASP.NET Core 应用程序、配置数据库提供程序以及设计应用程序的数据模型。
12.2 Adding EF Core to an application
12.2 将 EF Core 添加到应用程序
In this section we focus on getting EF Core installed and configured in your ASP.NET Core recipe API app. You’ll learn how to install the required NuGet packages and build the data model for your application. As we’re talking about EF Core in this chapter, I’m not going to go into how to create the application in general. I created a simple minimal API app as the basis—nothing fancy.
在本部分中,我们重点介绍如何在 ASP.NET Core 配方 API 应用程序中安装和配置 EF Core。您将了解如何安装所需的 NuGet 包并为您的应用程序构建数据模型。由于我们在本章中讨论的是 EF Core,因此我不打算一般地介绍如何创建应用程序。我创建了一个简单、最小的 API 应用程序作为基础 — 没什么花哨的。
Tip The sample code for this chapter shows the state of the application at three points in this chapter: at the end of section 12.2, at the end of section 12.3, and at the end of the chapter. It also includes examples using both LocalDB and SQLite providers.
提示 本章的示例代码显示了本章中三个点的应用程序状态:第 12.2 节的末尾、第 12.3 节的末尾和本章的末尾。它还包括使用 LocalDB 和 SQLite 提供程序的示例。
Interaction with EF Core in the example app occurs in a service layer that encapsulates all the data access outside your minimal API endpoint handlers, as shown in figure 12.4. This design keeps your concerns separated and makes your services testable.
在示例应用程序中与 EF Core 的交互发生在服务层中,该服务层封装了最小 API 端点处理程序之外的所有数据访问,如图 12.4 所示。这种设计将您的关注点分开,并使您的服务可测试。
Figure 12.4 Handling a request by loading data from a database using EF Core. Interaction with EF Core is restricted to RecipeService; the endpoint doesn’t access EF Core directly.
图 12.4 通过使用 EF Core 从数据库加载数据来处理请求。与 EF Core 的交互仅限于 RecipeService;终端节点不直接访问 EF Core。
Adding EF Core to an application is a multistep process:
将 EF Core 添加到应用程序是一个多步骤过程:
-
Choose a database provider, such as Postgres, SQLite, or MS SQL Server.
选择数据库提供程序,例如 Postgres、SQLite 或 MS SQL Server。 -
Install the EF Core NuGet packages.
安装 EF Core NuGet 包。 -
Design your app’s DbContext and entities that make up your data model.
设计应用程序的 DbContext 和构成数据模型的实体。 -
Register your app’s DbContext with the ASP.NET Core DI container.
将应用的 DbContext 注册到 ASP.NET Core DI 容器。 -
Use EF Core to generate a migration describing your data model.
使用 EF Core 生成描述数据模型的迁移。 -
Apply the migration to the database to update the database’s schema.
将迁移应用于数据库以更新数据库的架构。
This process may seem a little daunting already, but I’ll walk through steps 1–4 in sections 12.2.1–12.2.3 and steps 5–6 in section 12.3, so it won’t take long. Given the space constraints of this chapter, I stick to the default conventions of EF Core in the code I show you. EF Core is far more customizable than it may initially appear to be, but I encourage you to stick to the defaults wherever possible, which will make your life easier in the long run.
这个过程可能看起来有点令人生畏,但我将介绍第 12.2.1-12.2.3 节中的第 1-4 步和第 12.3 节中的第 5-6 步,因此不会花费很长时间。鉴于本章的篇幅限制,我在向您展示的代码中坚持 EF Core 的默认约定。EF Core 的可定制性比最初看起来要高得多,但我鼓励您尽可能坚持使用默认值,从长远来看,这将使您的生活更轻松。
The first step in setting up EF Core is deciding which database you’d like to interact with. It’s likely that a client or your company’s policy will dictate this decision, but giving some thought to it is still worthwhile.
设置 EF Core 的第一步是确定要与之交互的数据库。客户或您公司的政策可能会决定这个决定,但考虑一下仍然是值得的。
12.2.1 Choosing a database provider and installing EF Core
12.2.1 选择数据库提供程序并安装 EF Core
EF Core supports a range of databases by using a provider model. The modular nature of EF Core means that you can use the same high-level API to program against different underlying databases; EF Core knows how to generate the necessary implementation-specific code and SQL statements.
EF Core 使用提供程序模型支持一系列数据库。EF Core 的模块化特性意味着您可以使用相同的高级 API 对不同的底层数据库进行编程;EF Core 知道如何生成必要的特定于实现的代码和 SQL 语句。
You’ll probably have a database in mind when you start your application, and you’ll be pleased to know that EF Core has most of the popular ones covered. Adding support for a given database involves adding the correct NuGet package to your .csproj file, such as the following:
在启动应用程序时,您可能会想到一个数据库,并且您会很高兴地知道 EF Core 涵盖了大多数常用数据库。添加对给定数据库的支持涉及将正确的 NuGet 包添加到.csproj 文件,如下所示:
-
PostgreSQL—Npgsql.EntityFrameworkCore.PostgreSQL
-
Microsoft SQL Server—Microsoft.EntityFrameworkCore.SqlServer
-
MySQL—MySql.Data.EntityFrameworkCore
-
SQLite—Microsoft.EntityFrameworkCore.SQLite
Some of the database provider packages are maintained by Microsoft, some are maintained by the open-source community, and some (such as the Oracle provider) require a paid license, so be sure to check your requirements. You can find a list of providers at https://docs.microsoft.com/ef/core/providers.
一些数据库提供程序包由 Microsoft 维护,一些由开源社区维护,还有一些(如 Oracle 提供程序)需要付费许可证,因此请务必检查您的要求。您可以在 https://docs.microsoft.com/ef/core/providers 上找到提供商列表。
You install a database provider in your application in the same way as any other library: by adding a NuGet package to your project’s .csproj file and running dotnet restore from the command line (or letting Visual Studio automatically restore for you).
在应用程序中安装数据库提供程序的方式与任何其他库相同:将 NuGet 包添加到项目的 .csproj 文件并运行 dotnet restore从命令行(或让 Visual Studio 自动为您还原)。
EF Core is inherently modular, so you’ll need to install multiple packages. I’m using the SQLite database provider, so I’ll be using the SQLite packages:
EF Core 本质上是模块化的,因此需要安装多个包。我正在使用 SQLite 数据库提供程序,因此我将使用 SQLite 包:
-
Microsoft.EntityFrameworkCore.SQLite—This package is the main database provider package for using EF Core at runtime. It also contains a reference to the main EF Core NuGet package.
Microsoft.EntityFrameworkCore.SQLite – 此包是在运行时使用 EF Core 的主要数据库提供程序包。它还包含对主 EF Core NuGet 包的引用。 -
Microsoft.EntityFrameworkCore.Design—This package contains shared build-time components for EF Core, required for building the EF Core data model for your app.
Microsoft.EntityFrameworkCore.Design – 此包包含 EF Core 的共享构建时组件,这些组件是为您的应用程序构建 EF Core 数据模型所必需的。
Tip You’ll also want to install tooling to help create and update your database. I show how to install these tools in section 12.3.1.
提示 您还需要安装工具来帮助创建和更新数据库。我在 Section 12.3.1 中演示了如何安装这些工具。
Listing 12.1 shows the recipe app’s .csproj file after adding the EF Core packages. Remember, you add NuGet packages as PackageReference elements.
列表 12.1 显示了添加 EF Core 包后配方应用程序的 .csproj 文件。请记住,将 NuGet 包添加为 PackageReference 元素。
Listing 12.1 Installing EF Core in an ASP.NET Core application
列表 12.1 在 ASP.NET Core 中安装 EF Core应用
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework> ❶
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference ❷
Include="Microsoft.EntityFrameworkCore.SQLite" ❷
Version="7.0.0" /> ❷
<PackageReference ❸
Include="Microsoft.EntityFrameworkCore.Design" ❸
Version="7.0.0" > ❸
<IncludeAssets>runtime; build; native; contentfiles; ❹
Analyzers; buildtransitive</IncludeAssets> ❹
<PrivateAssets>all</PrivateAssets> ❹
</PackageReference>
</ItemGroup>
❶ The app targets .NET 7.0.
该应用程序面向 .NET 7.0。
❷ Installs the appropriate NuGet package for your selected DB
为所选数据库安装适当的 NuGet 包
❸ Contains shared design-time components for EF Core
包含 EF Core 的共享设计时组件
❹ Added automatically by NuGet
由 NuGet 自动添加
With these packages installed and restored, you have everything you need to start building the data model for your application. In section 12.2.2 we’ll create the entity classes and the DbContext for your recipe app.
安装并还原这些包后,您就拥有了开始为应用程序构建数据模型所需的一切。在第 12.2.2 节中,我们将为您的配方应用程序创建实体类和 DbContext。
12.2.2 Building a data model
12.2.2 构建数据模型
In section 12.1.4 I showed an overview of how EF Core builds up its internal model of your database from the DbContext and entity models. Apart from this discovery mechanism, EF Core is flexible in letting you define your entities the way you want to, as POCO classes.
在第 12.1.4 节中,我概述了 EF Core 如何从 DbContext 和实体模型构建其数据库的内部模型。除了这种发现机制之外,EF Core 还可以灵活地让你以所需的方式将实体定义为 POCO 类。
Some ORMs require your entities to inherit from a specific base class or require you to decorate your models with attributes that describe how to map them. EF Core heavily favors a convention over configuration approach, as you can see in listing 12.2, which shows the Recipe and Ingredient entity classes for your app.
某些 ORM 要求实体从特定基类继承,或者要求您使用描述如何映射模型的属性来装饰模型。EF Core 非常倾向于使用约定而不是配置方法,因为你可以这样做参见 清单 12.2 中,它显示了 Recipe 和ngredient 实体类。
Tip The required keyword, used on several properties in listing 12.2, was introduced in C# 11. It’s used here to prevent warnings about uninitialized non-nullable values. You can read more about how EF Core interacts with non-nullable types in the documentation at http://mng.bz/Keoj.
提示 在清单 12.2 中的几个属性上使用的 required 关键字是在 C# 11 中引入的。它在此处用于防止有关未初始化的不可为 null 值的警告。您可以在 http://mng.bz/Keoj 文档中阅读有关 EF Core 如何与不可为 null 的类型交互的更多信息。
Listing 12.2 Defining the EF Core entity classes
列表 12.2 定义 EF Core 实体类
public class Recipe
{
public int RecipeId { get; set; }
public required string Name { get; set; }
public TimeSpan TimeToCook { get; set; }
public bool IsDeleted { get; set; }
public required string Method { get; set; }
public required ICollection<Ingredient> Ingredients { get; set; } ❶
}
public class Ingredient
{
public int IngredientId { get; set; }
public int RecipeId { get; set; }
public required string Name { get; set; }
public decimal Quantity { get; set; }
public required string Unit { get; set; }
}
❶ A Recipe can have many Ingredients, represented by ICollection.
一个配方可以有很多成分,用 ICollection 表示。
These classes conform to certain default conventions that EF Core uses to build up a picture of the database it’s mapping. The Recipe class, for example, has a RecipeId property, and the Ingredient class has an IngredientId property. EF Core identifies this pattern of an Id suffix as indicating the primary key of the table.
这些类符合 EF Core 用于构建其映射的数据库图片的某些默认约定。例如,Recipe 类具有 RecipeId 属性,而 Ingredient 类具有 IngredientId 属性。EF Core 将 Id 后缀的这种模式标识为指示表的主键。
Definition The primary key of a table is a value that uniquely identifies the row among all the others in the table. It’s often an int or a Guid.
定义 表的主键是一个值,用于在表中的所有其他行中唯一标识该行。它通常是 int 或 Guid。
Another convention visible here is the RecipeId property on the Ingredient class. EF Core interprets this property to be a foreign key pointing to the Recipe class. When considered with ICollection
此处显示的另一个约定是 Ingredient 类的 RecipeId 属性。EF Core 将此属性解释为指向 Recipe 类的外键。当在 Recipe 类中使用 ICollection
Figure 12.5 Many-to-one relationships in code are translated to foreign key relationships between tables.
图 12.5 代码中的多对一关系转换为表之间的外键关系。
Definition A foreign key on a table points to the primary key of a different table, forming a link between the two rows.
定义 表上的外键指向不同表的主键,从而在两行之间形成链接。
Many other conventions are at play here, such as the names EF Core will assume for the database tables and columns or the database column types it will use for each property, but I’m not going to discuss them here. The EF Core documentation contains details about all these conventions, as well as how to customize them for your application; see https://docs.microsoft.com/ef/core/modeling.
这里还有许多其他约定,例如 EF Core 将为数据库表和列采用的名称,或者它将用于每个属性的数据库列类型,但我不打算在这里讨论它们。EF Core 文档包含有关所有这些约定以及如何为应用程序自定义它们的详细信息;请参阅 https://docs.microsoft.com/ef/core/modeling。
Tip You can also use DataAnnotations attributes to decorate your entity classes, controlling things like column naming and string length. EF Core will use these attributes to override the default conventions.
提示 您还可以使用 DataAnnotations 属性来装饰实体类,从而控制列命名和字符串长度等内容。EF Core 将使用这些属性来替代默认约定。
As well as defining the entities, you define the DbContext for your application. The DbContext is the heart of EF Core in your application, used for all your database calls. Create a custom DbContext, in this case called AppDbContext, and derive from the DbContext base class, as shown in listing 12.3. This class exposes the DbSet
除了定义实体之外,您还可以为应用程序定义 DbContext。DbContext 是应用程序中 EF Core 的核心,用于所有数据库调用。创建自定义 DbContext(在本例中称为 AppDbContext),并从 DbContext 基类派生,如清单所示12.3. 此类公开 DbSet
Listing 12.3 Defining the application DbContext
清单 12.3 定义应用程序 DbContext
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) ❶
: base(options) { } ❶
public DbSet<Recipe> Recipes { get; set; } ❷
}
❶ The constructor options object, containing details such as the connection string
构造函数选项对象,包含连接字符串等详细信息
❷ You’ll use the Recipes property to query the database.
您将使用 Recipes 属性来查询数据库。
The AppDbContext for your app is simple, containing a list of your root entities, but you can do a lot more with it in a more complex application. If you wanted to, you could customize how EF Core maps entities to the database, but for this app you’re going to use the defaults.
应用程序的 AppDbContext 很简单,包含根实体的列表,但您可以在更复杂的应用程序中使用它执行更多作。如果需要,可以自定义 EF Core 将实体映射到数据库的方式,但对于此应用程序,你将使用默认值。
Note You didn’t list Ingredient on AppDbContext, but EF Core models it correctly as it’s exposed on the Recipe. You can still access the Ingredient objects in the database, but you must navigate via the Recipe entity’s Ingredients property to do so, as you’ll see in section 12.4.
注意 您没有在 AppDbContext 上列出 Ingredient,但 EF Core 会正确建模,因为它在 Recipe 上公开。您仍然可以访问数据库中的 Ingredient 对象,但必须通过 Recipe 实体的 Ingredients 属性进行导航,如第 12.4 节所示。
For this simple example, your data model consists of these three classes: AppDbContext, Recipe, and Ingredient. The two entities are mapped to tables and their columns to properties, and you use the AppDbContext to access them.
对于这个简单的示例,您的数据模型由以下三个类组成:AppDbContext、Recipe 和 Ingredient。这两个实体映射到表,它们的列映射到属性,您可以使用 AppDbContext 访问它们。
Note This code-first approach is typical, but if you have an existing database, you can automatically generate the EF entities and DbContext instead. (You can find more information in Microsoft’s “reverse engineering” article at http://mng.bz/mgd4.)
注意 这种代码优先方法是典型的,但如果你有现有数据库,则可以自动生成 EF 实体和 DbContext。(您可以在 Microsoft 的“逆向工程”文章中找到更多信息,网址为 http://mng.bz/mgd4。
The data model is complete, but you’re not quite ready to use it: your ASP.NET Core app doesn’t know how to create your AppDbContext, and your AppDbContext needs a connection string so that it can talk to the database. In section 12.2.3 we tackle both of these problems, and we finish setting up EF Core in your ASP.NET Core app.
数据模型已完成,但您还没有完全准备好使用它:您的 ASP.NET Core 应用程序不知道如何创建 AppDbContext,并且您的 AppDbContext 需要一个连接字符串,以便它可以与数据库通信。在第 12.2.3 节中,我们解决了这两个问题,并完成了 ASP.NET Core 应用中的 EF Core 设置。
12.2.3 Registering a data context
12.2.3 注册数据上下文
As with any other service in ASP.Net Core, you should register your AppDbContext with the dependency injection (DI) container. When registering your context, you also configure the database provider and set the connection string so that EF Core knows how to talk with the database.
与 ASP.Net Core 中的任何其他服务一样,您应该向依赖项注入 (DI) 容器注册 AppDbContext。注册上下文时,还要配置数据库提供程序并设置连接字符串,以便 EF Core 知道如何与数据库通信。
You register the AppDbContext with the WebApplicationBuilder in Program.cs. EF Core provides a generic AddDbContext
您可以在 Program.cs 中向 WebApplicationBuilder 注册 AppDbContext。EF Core 为此提供了通用的 AddDbContext
The configuration for your app is, again, nice and simple, as you can see in the following listing. You set the database provider with the UseSqlite extension method, made available by the Microsoft.EntityFrameworkCore.SQLite package, and pass it a connection string.
同样,您的应用程序的配置既漂亮又简单,如下面的清单所示。您可以使用 UseSqlite 扩展方法(由 Microsoft.EntityFrameworkCore.SQLite 包提供)设置数据库提供程序,并向其传递连接字符串。
Listing 12.4 Registering a DbContext with the DI container
清单 12.4 向 DI 注册 DbContext容器
using Microsoft.EntityFrameworkCore;
WebApplicationBuillder builder = WebApplication.CreateBuilder(args);
var connString = builder.Configuration ❶
.GetConnectionString("DefaultConnection"); ❶
Builder.Services.AddDbContext<AppDbContext>( ❷
options => options.UseSqlite(connString)); ❸
WebApplication app = builder.Build();
app.Run();
❶ The connection string is taken from configuration, from the ConnectionStrings
section.
连接字符串取自配置的 ConnectionStrings 部分。
❷ Registers your app’s DbContext by using it as the generic parameter
通过将应用的 DbContext 用作泛型参数来注册该应用的 DbContext
❸ Specifies the database provider in the customization options for the
DbContext.
在 DbContext 的自定义选项中指定数据库提供程序。
Note If you’re using a different database provider, such as a provider for SQL Server, you need to call the appropriate Use method on the options object when registering your AppDbContext.
注意 如果您使用的是其他数据库提供程序(例如 SQL Server 的提供程序),则需要在注册 AppDbContext 时对 options 对象调用相应的 Use 方法。
The connection string is a typical secret, as I discussed in chapter 10, so loading it from configuration makes sense. At runtime the correct configuration string for your current environment is used, so you can use different databases when developing locally and in production.
正如我在第 10 章中讨论的那样,连接字符串是一个典型的密钥,因此从配置中加载它是有意义的。在运行时,将使用当前环境的正确配置字符串,因此在本地开发和生产时可以使用不同的数据库。
Tip You can configure your AppDbContext’s connection string in other ways, such as with the OnConfiguring method, but I recommend the method shown here for ASP.NET Core websites.
提示 您可以通过其他方式配置 AppDbContext 的连接字符串,例如使用 OnConfiguring 方法,但我建议对 ASP.NET Core 网站使用此处显示的方法。
Now you have a DbContext, named AppDbContext, registered as a scoped service with the DI container (typical for database-related services), and a data model corresponding to your database. Codewise, you’re ready to start using EF Core, but the one thing you don’t have is a database! In section 12.3 you’ll see how you can easily use the .NET CLI to ensure that your database stays up to date with your EF Core data model.
现在,您有一个名为 AppDbContext 的 DbContext,它注册为 DI 容器的作用域服务(通常用于数据库相关服务),以及与您的数据库对应的数据模型。在代码方面,你已准备好开始使用 EF Core,但你没有的一件事是数据库!在第 12.3 节中,你将了解如何轻松使用 .NET CLI 来确保数据库与 EF Core 数据模型保持同步。
12.3 Managing changes with migrations
12.3 通过迁移管理更改
In this section you’ll learn how to generate SQL statements to keep your database’s schema in sync with your application’s data model, using migrations. You’ll learn how to create an initial migration and use it to create the database. Then you’ll update your data model, create a second migration, and use it to update the database schema.
在本节中,您将学习如何使用迁移生成 SQL 语句,以使数据库的架构与应用程序的数据模型保持同步。您将了解如何创建初始迁移并使用它来创建数据库。然后,您将更新数据模型,创建第二个迁移,并使用它来更新数据库架构。
Managing schema changes for databases, such as when you need to add a new table or a new column, is notoriously difficult. Your application code is explicitly tied to a particular version of a database, and you need to make sure that the two are always in sync.
众所周知,管理数据库的架构更改(例如,当您需要添加新表或新列时)非常困难。您的应用程序代码明确绑定到数据库的特定版本,您需要确保两者始终同步。
Definition Schema refers to how the data is organized in a database, including the tables, columns, and relationships among them.
定义 架构是指数据在数据库中的组织方式,包括表、列以及它们之间的关系。
When you deploy an app, normally you can delete the old code/executable and replace it with the new code. Job done. If you need to roll back a change, delete that new code, and deploy an old version of the app.
部署应用程序时,通常可以删除旧代码/可执行文件并将其替换为新代码。工作完成。如果您需要回滚更改,请删除该新代码,然后部署旧版本的应用程序。
The difficulty with databases is that they contain data, so blowing it away and creating a new database with every deployment isn’t possible. A common best practice is to version a database’s schema explicitly along with your application’s code. You can do this in many ways, but typically you need to store the SQL script that takes the database from the previous schema to the new schema. Then you can use a library such as DbUp (https://github.com/DbUp/DbUp) or FluentMigrator (https://github.com/fluentmigrator/fluentmigrator) to keep track of which scripts have been applied and ensure that your database schema is up to date. Alternatively, you can use external tools to manage this task.
数据库的难点在于它们包含数据,因此不可能在每次部署时都将其吹走并创建一个新数据库。一种常见的最佳实践是将数据库的架构与应用程序代码一起显式进行版本控制。您可以通过多种方式执行此作,但通常需要存储将数据库从以前的架构转移到新架构的 SQL 脚本。然后,您可以使用 DbUp (https://github.com/DbUp/DbUp) 或 FluentMigrator (https://github.com/fluentmigrator/fluentmigrator) 等库来跟踪已应用的脚本,并确保您的数据库架构是最新的。或者,您也可以使用外部工具来管理此任务。
EF Core provides its own version of schema management called migrations. Migrations provide a way to manage changes to a database schema when your EF Core data model changes.
EF Core 提供自己的架构管理版本,称为迁移。 迁移提供了一种在 EF Core 数据模型更改时管理数据库架构更改的方法。
Definition A migration is a C# code file in your application that defines how the data model changed—which columns were added, new entities, and so on. Migrations provide a record over time of how your database schema evolved as part of your application, so the schema is always in sync with your app’s data model.
定义 迁移是应用程序中的 C# 代码文件,用于定义数据模型的更改方式 - 添加了哪些列、新实体等。迁移会记录数据库 Schema 作为应用程序的一部分如何随时间演变,因此 Schema 始终与应用的数据模型保持同步。
You can use command-line tools to create a new database from the migrations or to update an existing database by applying new migrations to it. You can even roll back a migration, which updates a database to a previous schema.
您可以使用命令行工具从迁移中创建新数据库,或者通过向现有数据库应用新的迁移来更新现有数据库。您甚至可以回滚迁移,这会将数据库更新到以前的架构。
Warning Applying migrations modifies the database, so you must always be aware of data loss. If you remove a table from the database using a migration and then roll back the migration, the table will be re-created, but the data it previously contained will be gone forever!
警告 应用迁移会修改数据库,因此您必须始终注意数据丢失。如果您使用迁移从数据库中删除表,然后回滚迁移,则会重新创建该表,但之前包含的数据将永远消失!
In this section, you’ll see how to create your first migration and use it to create a database. Then you’ll update your data model, create a second migration, and use it to update the database schema.
在本节中,您将了解如何创建您的第一个迁移并使用它来创建数据库。然后,您将更新数据模型,创建第二个迁移,并使用它来更新数据库架构。
12.3.1 Creating your first migration
12.3.1 创建您的第一个迁移
Before you can create migrations, you need to install the necessary tooling. You have two primary ways to do this:
在创建迁移之前,您需要安装必要的工具。有两种主要方法可以执行此作:
-
Package manager console—You can use PowerShell cmdlets inside Visual Studio’s Package Manager Console (PMC). You can install them directly from the PMC or by adding the Microsoft.EntityFrameworkCore.Tools package to your project.
包管理器控制台 — 您可以在 Visual Studio 的包管理器控制台 (PMC) 中使用 PowerShell cmdlet。您可以直接从 PMC 安装它们,也可以通过将 Microsoft.EntityFrameworkCore.Tools 包添加到您的项目来安装它们。 -
.NET tool—You can use cross-platform, command-line tooling that extends the .NET software development kit (SDK). You can install the EF Core .NET tool globally for your machine by running dotnet tool install --global dotnet-ef.
.NET 工具 — 您可以使用扩展 .NET SDK 的跨平台命令行工具。可以通过运行 dotnet tool install -- global dotnet-ef 为计算机全局安装 EF Core .NET 工具。
In this book I use the cross-platform .NET tools, but if you’re familiar with EF 6.x or prefer to use the Visual Studio PMC, there are equivalent commands for the steps you’re going to take (http://mng.bz/9DK7). You can check that the .NET tool installed correctly by running dotnet ef, which should produce a help screen like the one shown in figure 12.6.
在本书中,我使用了跨平台的 .NET 工具,但如果您熟悉 EF 6.x 或更喜欢使用 Visual Studio PMC,则对于您将要执行的步骤 (http://mng.bz/9DK7),有等效的命令。您可以通过运行 dotnet ef 来检查 .NET 工具是否正确安装,这应该会产生如图 12.6 所示的帮助屏幕。
Figure 12.6 Running the dotnet ef command to check that the .NET EF Core tools are installed correctly
图 12.6 运行 dotnet ef 命令以检查 .NET EF Core 工具是否已正确安装
Tip If you get the No executable found matching command ‘dotnet-ef’ message when running the preceding command, make sure that you installed the global tool by using dotnet tool install --global dotnet-ef. In general, you need to run the dotnet ef tools from the project folder in which you registered your AppDbContext—not from the solution-folder level.
提示 如果在运行上述命令时收到 No executable found matching command 'dotnet-ef' 消息,请确保使用 dotnet tool install --global 安装了全局工具dotnet-ef 的通常,您需要从注册 AppDbContext 的项目文件夹运行 dotnet ef 工具,而不是从解决方案文件夹级别运行。
With the tools installed and your database context configured, you can create your first migration by running the following command from inside your web project folder and providing a name for the migration (in this case, InitialSchema):
安装工具并配置数据库上下文后,您可以通过从 Web 项目文件夹内运行以下命令并提供迁移名称(在本例中为 InitialSchema)来创建第一个迁移:
dotnet ef migrations add InitialSchema
This command creates three files in the Migrations folder in your project:
此命令在项目的 Migrations 文件夹中创建三个文件:
-
Migration file—This file, with the Timestamp_MigrationName.cs format, describes the actions to take on the database, such as creating a table or adding a column. Note that the commands generated here are database-provider-specific, based on the database provider configured in your project.
迁移文件 - 此文件采用 Timestamp_MigrationName.cs 格式,描述要对数据库执行的作,例如创建表或添加列。请注意,此处生成的命令是特定于数据库提供程序的,具体取决于项目中配置的数据库提供程序。 -
Migration designer.cs file—This file describes EF Core’s internal model of your data model at the point in time when the migration was generated.
迁移designer.cs文件 – 此文件描述在生成迁移时数据模型的 EF Core 内部模型。 -
AppDbContextModelSnapshot.cs—This file describes EF Core’s current internal model. This file is updated when you add another migration, so it should always be the same as the current (latest) migration. EF Core can use AppDbContextModelSnapshot.cs to determine a database’s previous state when creating a new migration without interacting with the database directly.
AppDbContextModelSnapshot.cs – 此文件描述 EF Core 的当前内部模型。当您添加另一个迁移时,此文件会更新,因此它应始终与当前(最新)迁移相同。EF Core 可以在创建新迁移时使用 AppDbContextModelSnapshot.cs 来确定数据库的先前状态,而无需直接与数据库交互。
These three files encapsulate the migration process, but adding a migration doesn’t update anything in the database itself. For that task, you must run a different command to apply the migration to the database.
这三个文件封装了迁移过程,但添加迁移不会更新数据库本身的任何内容。对于该任务,您必须运行其他命令才能将迁移应用于数据库。
Tip You can, and should, look inside the migration file EF Core generates to check what it will do to your database before running the following commands. Better safe than sorry!
提示 在运行以下命令之前,您可以而且应该查看 EF Core 生成的迁移文件,以检查它将对数据库执行什么作。安全总比后悔好!
You can apply migrations in any of four ways:
您可以通过以下四种方式中的任何一种应用迁移:
- Using the .NET tool
使用 .NET 工具 - Using the Visual Studio PowerShell cmdlets
使用 Visual Studio PowerShell cmdlets - In code, by obtaining an instance of your AppDbContext from the DI container and calling context.Database.Migrate()
在代码中,通过获取AppDbContext 并从 DI 容器中调用上下文。Database.Migrate() 数据库 - By generating a migration bundle application (see http://mng.bz/jPyr)
通过生成迁移捆绑包应用程序(请参阅 http://mng.bz/jPyr)
Which method is best for you depends on how you designed your application, how you’ll update your production database, and what your personal preference is. I’ll use the .NET tool for now, but I discuss some of these considerations in section 12.5. You can apply migrations to a database by running
哪种方法最适合您取决于您如何设计应用程序、如何更新生产数据库以及您的个人偏好。我将使用.NET 工具,但我会在第 12.5 节中讨论其中的一些注意事项。您可以通过运行
dotnet ef database update
from the project folder of your application. I won’t go into the details on how this command works, but it performs four steps:
从应用程序的 project 文件夹中。我不会详细介绍此命令的工作原理,但它执行四个步骤:
- Builds your application
构建您的应用程序 - Loads the services configured in your app’s Program.cs, including AppDbContext
加载在应用程序的 Program.cs 中配置的服务,包括 AppDbContext - Checks whether the database in the AppDbContext connection string exists and if not, creates it
检查 AppDbContext 连接字符串中的数据库是否存在,如果不存在,则创建该数据库 - Updates the database by applying any unapplied migrations
通过应用任何未应用的迁移来更新数据库
If everything is configured correctly, as in section 12.2, running this command sets you up with a shiny new database like the one shown in figure 12.7.
如果一切都配置正确,如 Section 12.2 所示,运行此命令将为您设置一个闪亮的新数据库,如图 12.7 所示。
Figure 12.7 Applying migrations to a database creates the database if it doesn’t exist and updates the database to match EF Core’s internal data model. The list of applied migrations is stored in the EFMigrationsHistory table.
图 12.7 将迁移应用于数据库会创建数据库(如果数据库不存在),并更新数据库以匹配 EF Core 的内部数据模型。应用的迁移列表存储在 EFMigrationsHistory 表。
Note If you get an error message saying No project was found when running these commands, check that you’re running them in your application’s project folder, not the top-level solution folder.
注意 如果您在运行这些命令时收到一条错误消息,指出 No project was found,请检查您是否在应用程序的项目文件夹中运行它们,而不是在顶级解决方案文件夹中运行它们。
When you apply the migrations to the database, EF Core creates the necessary tables in the database and adds the appropriate columns and keys. You may have also noticed the __EFMigrationsHistory table, which EF Core uses to store the names of migrations that it’s applied to the database. Next time you run dotnet ef database update, EF Core can compare this table with the list of migrations in your app and apply only the new ones to your database.
将迁移应用于数据库时,EF Core 会在数据库中创建必要的表,并添加相应的列和键。你可能还注意到了 EFMigrationsHistory 表,EF Core 使用该表来存储它应用于数据库的迁移的名称。下次运行 dotnet ef database update 时,EF Core 可以将此表与应用中的迁移列表进行比较,并仅将新的迁移应用于数据库。
In section 12.3.2 we’ll look at how migrations make it easy to change your data model and update the database schema without having to re-create the database from scratch.
在 Section 12.3.2 中,我们将了解迁移如何使更改数据模型和更新数据库模式变得容易,而无需从头开始重新创建数据库。
12.3.2 Adding a second migration
12.3.2 添加第二个迁移
Most applications inevitably evolve due to increased scope or simple maintenance. Adding properties to your entities, adding new entities , and removing obsolete classes are all likely.
由于范围扩大或维护简单,大多数应用不可避免地会不断发展。向实体添加属性、添加新实体和删除过时的类都是可能的。
EF Core migrations make this evolution simple. Suppose that you decide to highlight vegetarian and vegan dishes in your recipe app by exposing IsVegetarian and IsVegan properties on the Recipe entity (listing 12.5). Change your entities to your desired state, generate a migration, and apply it to the database, as shown in figure 12.8.
EF Core 迁移使这种演变变得简单。假设您决定通过在 Recipe 实体(清单 12.5)上公开 IsVegetarian 和 IsVegan 属性,在食谱应用程序中突出显示素食和纯素食菜肴。将实体更改为所需状态,生成迁移并将其应用于数据库,如图 12.8 所示。
Figure 12.8 Creating a second migration and applying it to the database using the command-line tools.
图 12.8 使用命令行工具创建第二个迁移并将其应用于数据库
Listing 12.5 Adding properties to the Recipe entity
清单 12.5 向 Recipe 实体添加属性
public class Recipe
{
public int RecipeId { get; set; }
public required string Name { get; set; }
public TimeSpan TimeToCook { get; set; }
public bool IsDeleted { get; set; }
public required string Method { get; set; }
public bool IsVegetarian { get; set; }
public bool IsVegan { get; set; }
public required ICollection<Ingredient> Ingredients { get; set; }
}
As shown in figure 12.8, after changing your entities, you need to update EF Core’s internal representation of your data model. You perform this update exactly the same way that you did for the first migration, by calling dotnet ef migrations add and providing a name for the migration:
如图 12.8 所示,更改实体后,您需要更新 EF Core 的数据模型的内部表示形式。执行此更新的方式与第一次迁移完全相同,方法是调用 dotnet ef migrations add 并提供迁移的名称:
dotnet ef migrations add ExtraRecipeFields
This command creates a second migration in your project by adding the migration file and its .designer.cs snapshot file; it also updates AppDbContextModelSnapshot.cs (figure 12.9).
此命令通过添加迁移文件及其 .designer.cs 快照文件,在项目中创建第二个迁移;它还会更新 AppDbContextModelSnapshot.cs (图 12.9)。
Figure 12.9 Adding a second migration adds a new migration file and a migration Designer.cs file. It also updates AppDbContextModelSnapshot to match the new migration’s Designer.cs file.
图 12.9 添加第二个迁移会添加新的迁移文件和迁移Designer.cs文件。它还更新 AppDbContextModelSnapshot 以匹配新迁移的 Designer.cs 文件。
As before, this command creates the migration’s files but doesn’t modify the database. You can apply the migration and update the database by running
与以前一样,此命令会创建迁移的文件,但不会修改数据库。您可以通过运行
dotnet ef database update
This command compares the migrations in your application with the __EFMigrationsHistory table in your database to see which migrations are outstanding; then it runs them. EF Core runs the 20220825201452_ExtraRecipeFields migration, adding the IsVegetarian and IsVegan fields to the database, as shown in figure 12.10.
此命令将应用程序中的迁移与数据库中的 EFMigrationsHistory 表进行比较,以查看哪些迁移未完成;然后它运行它们。EF Core 运行 20220825201452_ExtraRecipeFields 迁移,将 IsVegetarian 和 IsVegan 字段添加到数据库中,如图 12.10 所示。
Figure 12.10 Applying the ExtraRecipeFields migration to the database adds the IsVegetarian and IsVegan fields to the Recipes table.
图 12.10 将 ExtraRecipeFields 迁移应用于数据库,将 IsVegetarian 和 IsVegan 字段添加到 Recipes 表中。
Using migrations is a great way to ensure that your database is versioned along with your app code in source control. You can easily check out your app’s source code for a historical point in time and re-create the database schema your application used at that point.
使用迁移是确保数据库与源代码管理中的应用程序代码一起进行版本控制的好方法。您可以轻松查看应用程序的历史时间点源代码,并重新创建应用程序在该时间点使用的数据库架构。
Migrations are easy to use when you’re working alone or deploying to a single web server, but even in these cases, you have important things to consider when deciding how to manage your databases. For apps with multiple web servers using a shared database or for containerized applications, you have even more things to think about.
当您单独工作或部署到单个 Web 服务器时,迁移很容易使用,但即使在这些情况下,在决定如何管理数据库时,您也需要考虑重要事项。对于具有多个 Web 服务器、使用共享数据库的应用程序或容器化应用程序,您需要考虑的事项更多。
This book is about ASP.NET Core, not EF Core, so I don’t want to dwell on database management much. But section 12.5 points out some of the things you need to bear in mind when using migrations in production.
这本书是关于 ASP.NET Core 的,而不是 EF Core,因此我不想过多地讨论数据库管理。但是 Section 12.5 指出了在 生产环境 中使用 migrations 时需要记住的一些事项。
In section 12.4 we’ll get back to the meaty stuff: defining our business logic and performing CRUD operations on the database.
在 Section 12.4 中,我们将回到内容丰富的东西:定义我们的业务逻辑并在数据库上执行 CRUD作。
12.4 Querying data from and saving data to the database
12.4 从数据库查询数据并将数据保存到数据库
Let’s review where you are in creating the recipe application:
让我们回顾一下您创建配方应用程序的位置:
-
You created a simple data model consisting of recipes and ingredients.
您创建了一个由配方和成分组成的简单数据模型。 -
You generated migrations for the data model to update EF Core’s internal model of your entities.
您为数据模型生成了迁移,以更新 EF Core 的实体内部模型。 -
You applied the migrations to the database so that its schema matches EF Core’s model.
您已将迁移应用于数据库,使其架构与 EF Core 的模型匹配。
In this section you’ll build the business logic for your application by creating a RecipeService. This service handles querying the database for recipes, creating new recipes, and modifying existing ones. As this app has a simple domain, I’ll be using RecipeService to handle all the requirements, but in your own apps you may have multiple services that cooperate to provide the business logic.
本节中,您将通过创建 RecipeService 来构建应用程序的业务逻辑。此服务处理在数据库中查询配方、创建新配方和修改现有配方。由于此应用程序具有一个简单的域,因此我将使用 RecipeService 来处理所有需求,但在您自己的应用程序中,您可能有多个服务相互协作以提供业务逻辑。
Note For simple apps, you may be tempted to move this logic into your endpoint handlers or Razor Pages. This approach may be fine for tiny apps, but I encourage you to resist the urge generally; extracting your business logic to other services decouples the HTTP-centric nature of your handlers from the underlying business logic, whichoften makes your business logic easier to test and more reusable.
注意 对于简单的应用程序,您可能会想将此逻辑移动到端点处理程序或 Razor Pages 中。这种方法可能适用于小型应用程序,但我鼓励您通常抵制这种冲动;将业务逻辑提取到其他服务可以将处理程序以 HTTP 为中心的性质与底层业务逻辑分离,这通常使业务逻辑更易于测试和更可重用。
Our database doesn’t have any data in it yet, so we’d better start by creating a recipe.
我们的数据库中还没有任何数据,因此我们最好先创建一个配方。
12.4.1 Creating a record
12.4.1 创建记录
In this section you’re going to build functionality to let users create a recipe by using the API. Clients send all the details of the recipe in the body of a POST request to an endpoint in your app. The endpoint uses model binding and validation attributes to confirm that the request is valid, as you learned in chapter 7.
在本节中,您将构建功能,让用户使用 API 创建配方。客户端将 POST 请求正文中配方的所有详细信息发送到 应用程序中的终端节点。终端节点使用模型绑定和验证属性来确认请求有效,如您在第 7 章中学到的那样。
If the request is valid, the endpoint handler calls RecipeService to create the new Recipe object in the database. As EF Core is the topic of this chapter, I’m going to focus on this service alone, but you can always check out the source code for this book if you want to see how everything fits together in a minimal API application.
如果请求有效,则端点处理程序会调用RecipeService 在数据库。由于 EF Core 是本章的主题,因此我将单独关注此服务,但如果您想了解所有内容如何组合到最小 API 应用程序中,您始终可以查看本书的源代码。
The business logic for creating a recipe in this application is simple: there is no logic! Copy the properties from the command binding model provided in the endpoint handler to a Recipe entity and its Ingredients, add the Recipe object to AppDbContext, and save it in the database, as shown in figure 12.11.
在此应用程序中创建配方的业务逻辑很简单:没有逻辑!将端点处理程序中提供的命令绑定模型中的属性复制到 Recipe 实体及其 Ingredients,将 Recipe 对象添加到 AppDbContext,并将其保存在数据库中,如图 12.11 所示。
Figure 12.11 Calling the POST endpoint and creating a new entity. A Recipe is created from the CreateRecipeCommand model and is added to the DbContext. EF Core generates the SQL to add a new row to the Recipes table in the database.
图 12.11 调用 POST 端点并创建新实体。配方是从 CreateRecipeCommand 模型创建的,并添加到 DbContext 中。EF Core 生成 SQL 以向数据库的 Recipes 表添加新行。
Warning Many simple, equivalent sample applications using EF or EF Core allow you to bind directly to the Recipe entity as the model in your endpoint. Unfortunately, this approach exposes a security vulnerability known as overposting, which is bad practice. If you want to avoid the boilerplate mapping code in your applications, consider using a library such as AutoMapper (http://automapper.org). For more details on overposting, see my blog post on the subject at http://mng.bz/d48O.
警告 许多使用 EF 或 EF Core 的简单等效示例应用程序允许您直接绑定到 Recipe 实体作为终端节点中的模型。不幸的是,这个方法会暴露一个称为 overpost 的安全漏洞,这是一种不好的做法。如果要避免在应用程序中使用样板映射代码,请考虑使用 AutoMapper (http://automapper.org) 等库。有关过度发布的更多详细信息,请参阅我在 http://mng.bz/d48O 上关于该主题的博客文章。
Creating an entity in EF Core involves adding a new row to the mapped table. For your application, whenever you create a new Recipe, you also add the linked Ingredient entities. EF Core takes care of linking all these entities correctly by creating the correct RecipeId for each Ingredient in the database.
在 EF Core 中创建实体涉及向映射表添加新行。对于您的应用程序,每当您创建新配方时,您都会添加链接的 Ingredient 实体。EF Core 通过为数据库中的每个成分创建正确的 RecipeId 来正确链接所有这些实体。
All interactions with EF Core and the database start with an instance of AppDbContext, which typically is DI-injected via the constructor. Creating a new entity requires three steps:
与 EF Core 和数据库的所有交互都从 AppDbContext 实例开始,该实例通常通过构造函数进行 DI 注入。创建新实体需要三个步骤:
-
Create the and Ingredient entities.
创建 Recipe 和 Ingredient 实体。 -
Add the entities to EF Core’s list of tracked entities using _context.Add(entity).
使用 _context 将实体添加到 EF Core 的跟踪实体列表中。 -
Execute the SQL INSERT statements against the database, adding the necessary rows to the Recipe and Ingredient tables, by calling _context.SaveChangesAsync().
对数据库执行 SQL INSERT 语句,通过调用SaveChangesAsync() 上下文。
Tip There are sync and async versions of most of the EF Core commands that involve interacting with the database, such as SaveChanges() and SaveChangesAsync(). In general, the async versions will allow your app to handle more concurrent connections, so I tend to favor them whenever I can use them.
提示 大多数涉及与数据库交互的 EF Core 命令都有同步和异步版本,例如 SaveChanges() 和 SaveChangesAsync()。通常,异步版本将允许您的应用程序处理更多的并发连接,因此只要可以使用它们,我就会倾向于使用它们。
Listing 12.6 shows these three steps in practice. The bulk of the code in this example involves copying properties from CreateRecipeCommand to the Recipe entity. The interaction with the AppDbContext consists of only two methods: Add() and SaveChangesAsync().
清单 12.6 展示了这三个步骤的实际应用。此示例中的大部分代码涉及将属性从 CreateRecipeCommand 复制到 Recipe 实体。与 AppDbContext 的交互仅包含两个方法:Add() 和 SaveChangesAsync()。
Listing 12.6 Creating a Recipe entity in the database in RecipeService
清单 12.6 在数据库中创建一个 Recipe 实体
readonly AppDbContext _context; ❶
public async Task<int> CreateRecipe(CreateRecipeCommand cmd) ❷
{
var recipe = new Recipe ❸
{ ❸
Name = cmd.Name, ❸
TimeToCook = new TimeSpan( ❸
cmd.TimeToCookHrs, cmd.TimeToCookMins, 0), ❸
Method = cmd.Method, ❸
IsVegetarian = cmd.IsVegetarian, ❸
IsVegan = cmd.IsVegan, ❸
Ingredients = cmd.Ingredients.Select(i => ❸
new Ingredient ❹
{ ❹
Name = i.Name, ❹
Quantity = i.Quantity, ❹
Unit = i.Unit, ❹
}).ToList() ❹
};
_context.Add(recipe); ❺
await _context.SaveChangesAsync(); ❻
return recipe.RecipeId; ❼
}
❶ An instance of the AppDbContext is injected in the class constructor using DI.
使用 DI 将 AppDbContext 的实例注入类构造函数中。
❷ CreateRecipeCommand is passed in from the endpoint handler.
CreateRecipeCommand 从端点处理程序传入。
❸ Creates a Recipe by mapping from the command object to the Recipe entity
通过从命令对象映射到 Recipe 实体来创建 Recipe
❹ Maps each CreateIngredientCommand onto an Ingredient entity
将每个 CreateIngredientCommand 映射到一个 Ingredient 实体
❺ Tells EF Core to track the new entities
告知 EF Core 跟踪新实体
❻ Tells EF Core to write the entities to the database; uses the async version of the command
告知 EF Core 将实体写入数据库;使用命令的异步版本
❼ EF Core populates the RecipeId field on your new Recipe when it’s saved.
保存新配方时,EF Core 会填充新配方上的 RecipeId 字段。
If a problem occurs when EF Core tries to interact with your database—you haven’t run the migrations to update the database schema, for example—this code throws an exception. I haven’t shown it here, but it’s important to handle these exceptions in your application so you don’t present an ugly error message to user when things go wrong.
如果在 EF Core 尝试与数据库交互时出现问题(例如,您尚未运行迁移来更新数据库架构),则此代码将引发异常。我没有在这里展示它,但在您的应用程序中处理这些异常很重要,这样您就不会在出现问题时向用户显示难看的错误消息。
Assuming that all goes well, EF Core updates all the autogenerated IDs of your entities (RecipeId on Recipe, and both RecipeId and IngredientId on Ingredient). Return the recipe ID to the endpoint handler so the handler can use it—to return the ID in the API response, for example.
假设一切顺利,EF Core 会更新实体的所有自动生成的 ID(Recipe 上的 RecipeId,以及 Ingredient 上的 RecipeId 和 IngredientId)。将配方 ID 返回给终端节点处理程序,以便处理程序可以使用它,例如,在 API 响应中返回 ID。
Tip The DbContext type is an implementation of both the unit-of-work and repository patterns, so you generally don’t need to implement these patterns manually in your apps. You can read more about these patterns at https://martinfowler.com/eaaCatalog.
提示 DbContext 类型是工作单元模式和存储库模式的实现,因此您通常不需要在应用程序中手动实现这些模式。您可以在 https://martinfowler.com/eaaCatalog 上阅读有关这些模式的更多信息。
And there you have it. You’ve created your first entity with EF Core. In section 12.4.2 we’ll look at loading these entities from the database so you can fetch them all in a list.
你有它。你已使用 EF Core 创建了第一个实体。在 Section 12.4.2 中,我们将介绍如何从数据库中加载这些实体,以便您可以在列表中获取它们。
12.4.2 Loading a list of records
12.4.2 加载记录列表
Now that you can create recipes, you need to write the code to view them. Luckily, loading data is simple in EF Core, relying heavily on LINQ methods to control the fields you need. For your app, you’ll create a method on RecipeService that returns a summary view of a recipe, consisting of RecipeId, Name, and TimeToCook as a RecipeSummaryViewModel, as shown in figure 12.12.
现在,您可以创建配方,您需要编写代码来查看它们。幸运的是,在 EF Core 中加载数据很简单,在很大程度上依赖于 LINQ 方法来控制所需的字段。对于您的应用程序,您将在 RecipeService 上创建一个方法,该方法返回配方的摘要视图,其中包含 RecipeId、Name 和 TimeToCook 作为 RecipeSummaryViewModel,如图 12.12 所示。
Figure 12.12 Calling the GET list endpoint and querying the database to retrieve a list of RecipeSummaryViewModels. EF Core generates the SQL to retrieve the necessary fields from the database and maps them to view model objects.
图 12.12 调用 GET 列表终端节点并查询数据库以检索 RecipeSummaryViewModels 列表。EF Core 生成 SQL 以从数据库中检索必要的字段,并将它们映射到视图模型对象。
Note Creating a view model is technically a UI concern rather than a business-logic concern. I’m returning a view model directly from RecipeService here mostly to hammer home the fact that you shouldn’t be using EF Core entities directly in your endpoint’s public API. Alternatively, you might return the Recipe entity directly from the RecipeService and then build and return the RecipeSummaryViewModel inside your endpoint handler code.
注意 从技术上讲,创建视图模型是一个 UI 问题,而不是业务逻辑问题。我在这里直接从 RecipeService 返回一个视图模型,主要是为了强调您不应该直接在终端节点的公共 API 中使用 EF Core 实体的事实。或者,您可以直接从 RecipeService 返回 Recipe 实体,然后在终端节点处理程序代码中构建并返回 RecipeSummaryViewModel。
The GetRecipes method in RecipeService is conceptually simple and follows a common pattern for querying an EF Core database, as shown in figure 12.13. EF Core uses a fluent chain of LINQ commands to define the query to return on the database. The DbSet
RecipeService 中的 GetRecipes 方法在概念上很简单,并遵循查询 EF Core 数据库的常见模式,如图 12.13 所示。EF Core 使用 Fluent LINQ 命令链来定义要在数据库上返回的查询。AppDataContext 上的 DbSet
You can also use the Select() extension method to map to objects other than your entities as part of the SQL query. You can use this technique to query the database efficiently by fetching only the columns you need.
您还可以使用 Select() 扩展方法映射到实体以外的对象,作为 SQL 查询的一部分。您可以使用此技术通过仅获取所需的列来高效地查询数据库。
Figure 12.13 The three parts of an EF Core database query
图 12.13 EF Core 数据库查询的三个部分
Listing 12.7 shows the code to fetch a list of RecipeSummaryViewModels, following the same basic pattern as figure 12.12. It uses a Where LINQ expression to filter out recipes marked as deleted and a Select clause to map to the view models. The ToListAsync() command instructs EF Core to generate the SQL query, execute it on the database, and build RecipeSummaryViewModels from the data returned.
清单 12.7 显示了获取 RecipeSummaryViewModel列表的代码,遵循与图 12.12 相同的基本模式。它使用 Where LINQ 表达式筛选出标记为已删除的配方,并使用 Select 子句映射到视图模型。ToListAsync() 命令指示 EF Core 生成 SQL 查询,在数据库上执行该查询,并根据返回的数据生成 RecipeSummaryViewModels。
Listing 12.7 Loading a list of items using EF Core in RecipeService
清单 12.7 在配方服务
public async Task<ICollection<RecipeSummaryViewModel>> GetRecipes()
{
return await _context.Recipes ❶
.Where(r => !r.IsDeleted)
.Select(r => new RecipeSummaryViewModel ❷
{ ❷
Id = r.RecipeId, ❷
Name = r.Name, ❷
TimeToCook = $"{r.TimeToCook.TotalMinutes}mins" ❷
})
.ToListAsync(); ❸
}
❶ A query starts from a DbSet property.
查询从 DbSet 属性开始。
❷ EF Core queries only the Recipe columns it needs to map the view model
correctly.
EF Core 仅查询正确映射视图模型所需的 Recipe 列。
❸ Executes the SQL query and creates the final view models
执行 SQL 查询并创建最终视图模型
Notice that in the Select method you convert the TimeToCook property from a TimeSpan to a string by using string interpolation:
请注意,在 Select 方法中,通过使用字符串插值将 TimeToCook 属性从 TimeSpan 转换为字符串:
TimeToCook = $"{r.TimeToCook.TotalMinutes}mins"
I said before that EF Core converts the series of LINQ expressions to SQL, but that statement is a half-truth: EF Core can’t or doesn’t know how to convert some expressions to SQL. In those cases, such as this example, EF Core finds the fields from the DB that it needs to run the expression on the client side, selects them from the database, and then runs the expression in C#. This approach lets you combine the power and performance of database-side evaluation without compromising the functionality of C#.
我之前说过 EF Core 将一系列 LINQ 表达式转换为 SQL,但该说法是半真半假:EF Core 不能或不知道如何将某些表达式转换为 SQL。在这些情况下(例如本示例),EF Core 从数据库中找到在客户端运行表达式所需的字段,从数据库中选择这些字段,然后在 C# 中运行表达式。此方法允许您将数据库端评估的强大功能和性能相结合,而不会影响 C# 的功能。
Warning Client-side evaluation is both powerful and useful but has the potential to cause problems. In general, recent versions of EF Core throw an exception if a query requires dangerous client-side evaluation, ensuring (for example) that you can’t accidentally return all records to the client before filtering. For more examples, including ways to avoid these problems, see the documentation at http://mng.bz/zxP6.
警告 客户端评估功能强大且有用,但可能会导致问题。通常,如果查询需要危险的客户端评估,最新版本的 EF Core 会引发异常,例如,确保在筛选之前不会意外地将所有记录返回给客户端。有关更多示例,包括避免这些问题的方法,请参阅 http://mng.bz/zxP6 中的文档。
At this point, you have a list of records displaying a summary of the recipe’s data, so the obvious next step is loading the detail for a single record.
此时,您有一个记录列表,其中显示了配方数据的摘要,因此显而易见的下一步是加载单个记录的详细信息。
12.4.3 Loading a single record
12.4.3 加载单个记录
For most intents and purposes, loading a single record is the same as loading a list of records. Both approaches have the same common structure you saw in figure 12.13, but when you’re loading a single record, you typically use a Where clause that restricts the data to a single entity.
对于大多数 intent 和目的,加载单个记录与加载记录列表相同。这两种方法都具有您在图 12.13 中看到的相同的通用结构,但是当您加载单个记录时,您通常会使用 Where 子句将数据限制为单个实体。
Listing 12.8 shows the code to fetch a recipe by ID, following the same basic pattern as before (figure 12.12). It uses a Where() LINQ expression to restrict the query to a single recipe, where RecipeId == id, and a Select clause to map to RecipeDetailViewModel. The SingleOrDefaultAsync() clause causes EF Core to generate the SQL query, execute it on the database, and build the view model.
清单 12.8 显示了通过 ID 获取配方的代码,遵循与之前相同的基本模式(图 12.12)。它使用 Where() LINQ 表达式将查询限制为单个配方,其中 RecipeId == id,并使用 Select 子句映射到 RecipeDetailViewModel。SingleOrDefaultAsync() 子句使 EF Core 生成 SQL 查询,在数据库上执行该查询,并生成视图模型。
Note SingleOrDefaultAsync()throws an exception if the previous Where clause returns more than one record.
注意 SingleOrDefaultAsync() 如果前面的 Where 子句返回多条记录,则引发异常。
Listing 12.8 Loading a single item using EF Core in RecipeService
清单 12.8 在配方服务
public async Task<RecipeDetailViewModel> GetRecipeDetail(int id) ❶
{
return await _context.Recipes ❷
.Where(x => x.RecipeId == id) ❸
.Select(x => new RecipeDetailViewModel ❹
{ ❹
Id = x.RecipeId, ❹
Name = x.Name, ❹
Method = x.Method, ❹
Ingredients = x.Ingredients ❺
.Select(item => new RecipeDetailViewModel.Item ❺
{ ❺
Name = item.Name, ❺
Quantity = $"{item.Quantity} {item.Unit}" ❺
}) ❺
})
.SingleOrDefaultAsync(); ❻
}
❶ The id of the recipe to load is passed as a parameter.
要加载的配方的 id 作为参数传递。
❷ As before, a query starts from a DbSet property.
与以前一样,查询从 DbSet 属性开始。
❸ Limits the query to the recipe with the provided id
将查询限制为具有提供的 ID 的配方
❹ Maps the Recipe to a RecipeDetailViewModel
将配方映射到 RecipeDetailViewModel
❺ Loads and maps linked Ingredients as part of the same query
加载和映射链接的 Ingredients 作为同一查询的一部分
❻ Executes the query and maps the data to the view model
执行查询并将数据映射到视图模型
Notice that as well as mapping the Recipe to a RecipeDetailViewModel, you map the related Ingredients for a Recipe, as though you’re working with the objects directly in memory. One advantage of using an ORM is that you can easily map child objects and let EF Core decide how best to build the underlying queries to fetch the data.
请注意,除了将 Recipe 映射到 RecipeDetailViewModel 之外,您还可以映射 Recipe 的相关 Ingredient,就像您直接在内存中处理对象一样。使用 ORM 的一个优点是,您可以轻松映射子对象,并让 EF Core 决定如何最好地构建基础查询来提取数据。
Note EF Core logs all the SQL statements it runs as LogLevel.Information events by default, so you can easily see what queries are running against the database.
注意 默认情况下,EF Core 将其运行的所有 SQL 语句记录为 LogLevel.Information 事件,因此您可以轻松查看针对数据库运行的查询。
Your app is definitely shaping up. You can create new recipes, view them all in a list, and drill down to view individual recipes with their ingredients and method. Soon, though, someone’s going to introduce a typo and want to change their data, so you’ll have to implement the U in CRUD: update.
您的应用肯定正在成型。您可以创建新配方,在列表中查看所有配方,并向下钻取以查看单个配方食谱及其成分和方法。不过,很快,有人会引入一个拼写错误并想要更改他们的数据,因此您必须在 CRUD: update 中实现 U。
12.4.4 Updating a model with changes
12.4.4 使用更改更新模型
Updating entities when they’ve changed generally is the hardest part of CRUD operations, as there are so many variables. Figure 12.14 shows an overview of this process as it applies to your recipe app.
在实体发生更改时更新实体通常是 CRUD作中最困难的部分,因为变量太多了。图 12.14 显示了适用于您的配方应用程序的此过程的概述。
Figure 12.14 Updating an entity involves three steps: read the entity using EF Core, update the properties of the entity, and call SaveChangesAsync() on the DbContext to generate the SQL to update the correct rows in the database.
图 12.14 更新实体涉及三个步骤:使用 EF Core 读取实体,更新实体的属性,并在 DbContext 上调用 SaveChangesAsync() 以生成 SQL 以更新数据库中的正确行。
I’m not going to handle the relationship aspect in this book because that problem generally is complex, and how you tackle it depends on the specifics of your data model. Instead, I’ll focus on updating properties on the Recipe entity itself.
我不打算在本书中处理关系方面,因为这个问题通常很复杂,如何解决它取决于数据模型的具体情况。相反,我将重点介绍更新 Recipe 上的属性实体本身。
Note For a detailed discussion of handling relationship updates in EF Core, see Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021; http://mng.bz/w9D2).
注意 有关在 EF Core 中处理关系更新的详细讨论,请参阅 Jon P. Smith 的 Entity Framework Core in Action,第 2 版(Manning,2021 年; http://mng.bz/w9D2)。
For web applications, when you update an entity you typically follow the steps outlined in figure 12.14:
对于 Web 应用程序,当您更新实体时,通常会按照图 12.14 中概述的步骤进行作:
- Read the entity from the database.
从数据库中读取实体。 - Modify the entity’s properties.
修改实体的属性。
3.Save the changes to the database.
保存对数据库的更改。
You’ll encapsulate these three steps in a method on RecipeService called UpdateRecipe. This method takes an UpdateRecipeCommand parameter and contains the code to change the Recipe entity.
您将把这三个步骤封装在 RecipeService 上名为 UpdateRecipe 的方法中。此方法采用 UpdateRecipeCommand 参数,并包含用于更改 Recipe 实体的代码。
Note As with the Create command, you don’t modify the entities directly in the minimal API endpoint handler, ensuring that you keep the UI/API concern separate from the business logic.
注意 与 Create 命令一样,您不会直接在最小 API 终端节点处理程序中修改实体,从而确保将 UI/API 关注点与业务逻辑分开。
Listing 12.9 shows the RecipeService.UpdateRecipe method, which updates the Recipe entity. It performs the three steps we defined previously to read, modify, and save the entity. I’ve extracted the code to update the recipe with the new values to a helper method for clarity.
清单 12.9 显示了 RecipeService.UpdateRecipe 方法,该方法更新了 Recipe 实体。它执行我们之前定义的三个步骤来读取、修改和保存实体。为清楚起见,我提取了代码,以使用新值将配方更新为帮助程序方法。
Listing 12.9 Updating an existing entity with EF Core in RecipeService
清单 12.9 使用 EF Core 更新现有实体在 RecipeService 中
public async Task UpdateRecipe(UpdateRecipeCommand cmd)
{
var recipe = await _context.Recipes.FindAsync(cmd.Id); ❶
if(recipe is null) { ❷
throw new Exception("Unable to find the recipe"); ❷
} ❷
UpdateRecipe(recipe, cmd); ❸
await _context.SaveChangesAsync(); ❹
}
static void UpdateRecipe(Recipe recipe, UpdateRecipeCommand cmd) ❺
{ ❺
recipe.Name = cmd.Name; ❺
recipe.TimeToCook = ❺
new TimeSpan(cmd.TimeToCookHrs, cmd.TimeToCookMins, 0); ❺
recipe.Method = cmd.Method; ❺
recipe.IsVegetarian = cmd.IsVegetarian; ❺
recipe.IsVegan = cmd.IsVegan; ❺
} ❺
❶ Find is exposed directly by Recipes and simplifies reading an entity by id.
Find 由 Recipes 直接公开,并简化了按 ID 读取实体的过程。
❷ If an invalid id is provided, recipe will be null.
如果提供的 ID 无效,则 recipe 将为 null。
❸ Sets the new values on the Recipe entity
在 Recipe 实体上设置新值
❹ Executes the SQL to save the changes to the database
执行 SQL 以保存对数据库的更改
❺ A helper method for setting the new properties on the Recipe entity
用于在 Recipe 实体上设置新属性的辅助方法
In this example I read the Recipe entity using the FindAsync(id) method exposed by DbSet. This simple helper method loads an entity by its ID—in this case, RecipeId. I could have written a similar query with LINQ:
在此示例中,我使用 DbSet 公开的 FindAsync(id) 方法读取 Recipe 实体。这个简单的帮助程序方法按实体的 ID 加载实体,在本例中为 RecipeId。我本可以使用 LINQ 编写类似的查询:
_context.Recipes.Where(r=>r.RecipeId == cmd.Id).FirstOrDefault();
Using FindAsync() or Find() is a little more declarative and concise, however.
但是,使用 FindAsync() 或 Find() 的声明性和简洁性更强一些。
Tip Find is a bit more complicated. Find first checks to see whether the entity is already being tracked in EF Core’s DbContext. If so (because the entity was previously loaded in this request), the entity is returned immediately without calling the database. Using Find can obviously be faster if the entity is tracked, but it can be slower if you know that the entity isn’t being tracked yet.
提示 Find 稍微复杂一些。“查找优先”检查是否已在 EF Core 的 DbContext 中跟踪实体。如果是这样(因为之前在此请求中加载了实体),则立即返回该实体,而不调用数据库。如果跟踪实体,则使用 Find 显然会更快,但如果您知道尚未跟踪实体,则使用 Find 可能会更慢。
You may wonder how EF Core knows which columns to update when you call SaveChangesAsync(). The simplest approach would be to update every column. If the field hasn’t changed, it doesn’t matter if you write the same value again. But EF Core is cleverer than that.
你可能想知道 EF Core 在调用 SaveChangesAsync() 时如何知道要更新哪些列。最简单的方法是更新每一列。如果字段未更改,则再次写入相同的值并不重要。但 EF Core 比这更聪明。
EF Core internally tracks the state of any entities it loads from the database and creates a snapshot of all the entity’s property values so that it can track which ones have changed. When you call SaveChanges(), EF Core compares the state of any tracked entities (in this case, the Recipe entity) with the tracking snapshot. Any properties that have been changed are included in the UPDATE statement sent to the database, and unchanged properties are ignored.
EF Core 在内部跟踪它从数据库加载的任何实体的状态,并创建所有实体属性值的快照,以便它可以跟踪哪些实体已更改。调用 SaveChanges() 时,EF Core 会将任何跟踪实体(在本例中为 Recipe 实体)的状态与跟踪快照进行比较。任何已更改的属性都包含在发送到数据库的 UPDATE 语句中,而未更改的属性将被忽略。
Note EF Core provides other mechanisms to track changes, as well as options to disable change tracking. See the documentation or chapter 3 of Jon P. Smith’s Entity Framework Core in Action, 2nd ed., (Manning, 2021; http://mng.bz/q9PJ) for details. You can view which details the DbContext is tracking by accessing DbContext.ChangeTracer.DebugView, as described in the documentation at http://mng.bz/8rlz.
注意 EF Core 提供了其他机制来跟踪更改,以及用于禁用更改跟踪的选项。请参阅 Jon P. Smith 的 Entity Framework Core in Action, 2nd ed.(Manning,2021 年;http://mng.bz/q9PJ)了解详情。您可以通过访问 DbContext.ChangeTracer.DebugView 来查看 DbContext 正在跟踪的详细信息,如 http://mng.bz/8rlz 中的文档中所述。
With the ability to update recipes, you’re almost done with your recipe app. “But wait!” I hear you cry. “we haven’t handled the D in CRUD: delete!” That’s true, but in reality, I’ve found only a few occasions to delete data. Let’s consider the requirements for deleting a recipe from the application:
借助更新食谱的功能,您几乎完成了食谱应用程序。“但是等等!”我听到你哭泣。“我们还没有处理 CRUD 中的 D:删除!”这是真的,但实际上,我只发现了少数删除数据的机会。让我们考虑一下从应用程序中删除配方的要求:
-
You need to provide an API that deletes a recipe.
您需要提供用于删除配方的 API。 -
After a recipe is deleted, it must not appear in the recipe list and can’t be retrieved.
删除配方后,它不得显示在配方列表中,也无法检索。
You could achieve these requirements by deleting the recipe from the database, but the problem with data is that when it’s gone, it’s gone! What if a user accidentally deletes a record? Also, deleting a row from a relational database typically has implications on other entities. You can’t delete a row from the Recipe table in your application, for example, without also deleting all the Ingredient rows that reference it, thanks to the foreign-key constraint on Ingredient.RecipeId.
您可以通过从数据库中删除配方来实现这些要求,但数据的问题在于,当它消失时,它就消失了!如果用户不小心删除了记录怎么办?此外,从关系数据库中删除行通常会对其他实体产生影响。例如,由于对 Ingredient.RecipeId 的外键约束,您无法从应用程序的 Recipe 表中删除一行,而无需删除引用该行的所有 Ingredient 行。
EF Core can easily handle these true deletion scenarios for you with the DbContext .Remove(entity) command, but often what you mean when you find a need to delete data is to archive it or hide it from the UI. A common approach to handling this scenario is to include some sort of “Is this entity deleted?” flag on your entity, such as the IsDeleted flag I included on the Recipe entity:
EF Core 可以使用 DbContext 轻松处理这些真正的删除方案。Remove(entity) 命令,但当您发现需要删除数据时,您的意思是将其存档或从 UI 中隐藏数据。处理这种情况的常见方法是包括某种“Is this entity deleted?” 标志,例如IsDeleted 标志:
public bool IsDeleted { get; set; }
If you take this approach, deleting data suddenly becomes simpler, as it’s nothing more than an update to the entity—no more problems of lost data and no more referential-integrity problems.
如果您采用这种方法,删除数据会突然变得更加简单,因为它只不过是对实体的更新——不再有数据丢失的问题,也不再有引用完整性问题。
Note The main exception I’ve found to this pattern is when you’re storing your users’ personally identifying information. In these cases, you may be duty-bound (and potentially legally bound) to scrub their information from your database on request.
注意 我发现这种模式的主要例外是当您存储用户的个人身份信息时。在这些情况下,您可能有义务(并且可能受法律约束)根据要求从您的数据库中删除他们的信息。
With this approach, you can create a delete method on RecipeService that updates the IsDeleted flag, as shown in listing 12.10. In addition, make sure that you have Where() clauses in all the other methods in your RecipeService to ensure you can’t return a deleted Recipe, as you saw in listing 12.9 for the GetRecipes() method.
使用这种方法,你可以在 RecipeService 上创建一个 delete 方法来 更新 IsDeleted 标志,如清单 12.10 所示。此外,请确保在 RecipeService 的所有其他方法中都有 Where() 子句,以确保无法返回已删除的 Recipe,如清单 12.9 中的 GetRecipes() 方法所示。
Listing 12.10 Marking entities as deleted in EF Core
列表 12.10 在 EF Core 中将实体标记为已删除
public async Task DeleteRecipe(int recipeId)
{
var recipe = await _context.Recipes.FindAsync(recipeId); ❶
if(recipe is null) { ❷
throw new Exception("Unable to find the recipe"); ❷
} ❷
recipe.IsDeleted = true; ❸
await _context.SaveChangesAsync(); ❹
}
❶ Fetches the Recipe entity by id
按 id 获取 Recipe 实体
❷ If an invalid id is provided, recipe will be null.
如果提供的 ID 无效,则 recipe 将为 null。
❸ Marks the Recipe as deleted
将配方标记为已删除
❹ Executes the SQL to save the changes to the database
行 SQL 以保存对数据库的更改
This approach satisfies the requirements—it removes the recipe from exposure by the API—but it simplifies several things. This soft-delete approach won’t work for all scenarios, but I’ve found it to be a common pattern in projects I’ve worked on.
这种方法满足了要求,它消除了 API 公开的配方,但它简化了几件事。这种软删除方法并不适用于所有情况,但我发现它是我参与过的项目中的常见模式。
Tip EF Core has a handy feature called global query filters. These filters allow\ you to specify a Where clause at the model level. You could ensure, for example, that EF Core never loads Recipes for which IsDeleted is true. This feature is also useful for segregating data in a multitenant environment. See the documentation for details: http://mng.bz/EQxd.
提示 EF Core 有一个称为全局查询筛选器的便捷功能。这些筛选条件允许您在模型级别指定 Where 子句。例如,您可以确保 EF Core 永远不会加载 IsDeleted 为 true 的配方。此功能对于在多租户环境中隔离数据也很有用。有关详细信息,请参阅文档:http://mng.bz/EQxd。
We’re almost at the end of this chapter on EF Core. We’ve covered the basics of adding EF Core to your project and using it to simplify data access, but you’ll likely need to learn more about EF Core as your apps become more complex. In the final section of this chapter, I’d like to pinpoint a few things you need to take into consideration before using EF Core in your own applications so that you’ll be familiar with some of the problems you’ll face as your apps grow.
关于 EF Core 的本章即将结束。我们已经介绍了将 EF Core 添加到项目并使用它来简化数据访问的基础知识,但随着应用变得越来越复杂,你可能需要了解有关 EF Core 的更多信息。在本章的最后一部分中,我想指出在您自己的应用程序中使用 EF Core 之前需要考虑的一些事项,以便您熟悉随着应用程序的增长而面临的一些问题。
12.5 Using EF Core in production applications
12.5 在生产应用程序中使用 EF Core
This book is about ASP.NET Core, not EF Core, so I didn’t want to spend too much time exploring EF Core. This chapter should’ve given you enough information to get up and running, but you definitely need to learn more before you even think about putting EF Core into production. As I’ve said several times, I recommend reading Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021), or exploring the EF Core documentation site at https://docs.microsoft.com/ef/core.
这本书是关于 ASP.NET Core 的,而不是 EF Core,因此我不想花太多时间探索 EF Core。本章应该已经为你提供了足够的信息来启动和运行,但你肯定需要在考虑将 EF Core 投入生产之前了解更多信息。正如我多次说过的,我建议阅读 Jon P. Smith 的 Entity Framework Core in Action,第 2 版(Manning,2021 年),或浏览 https://docs.microsoft.com/ef/core 的 EF Core 文档站点。
The following topics aren’t essential for getting started with EF Core, but you’ll quickly run up against them if you build a production-ready app. This section isn’t a prescriptive guide to tackling each of these items, but more a set of things to consider before you dive into production:
以下主题对于开始使用 EF Core 不是必需的,但如果您构建生产就绪型应用程序,您将很快遇到这些主题。本节不是处理这些项目的规范性指南,而是在深入研究生产之前需要考虑的一组事项:
-
Scaffolding of columns—EF Core uses conservative values for things like string columns by allowing strings of large or unlimited length. In practice, you may want to restrict these and other data types to sensible values.
列的基架 – EF Core 通过允许较大或无限长度的字符串,对字符串列等内容使用保守值。在实践中,您可能希望将这些数据类型和其他数据类型限制为合理的值。 -
Validation—You can decorate your entities with DataAnnotations validation attributes, but EF Core won’t validate the values automatically before saving to the database. This behavior differs from EF 6.x behavior, in which validation was automatic.
验证 – 您可以使用 DataAnnotations 验证属性修饰实体,但 EF Core 不会在保存到数据库之前自动验证值。此行为不同于 EF 6.x 行为,在 EF 6.x 中,验证是自动的。 -
Handling concurrency—EF Core provides a few ways to handle concurrency, which occurs when multiple users attempt to update an entity at the same time. One partial solution is to use Timestamp columns on your entities.
处理并发 – EF Core 提供了几种处理并发的方法,当多个用户尝试同时更新实体时,会发生并发。一种部分解决方案是在实体上使用 Timestamp 列。 -
Handling errors—Databases and networks are inherently flaky, so you’ll always have to account for transient errors. EF Core includes various features to maintain connection resiliency by retrying on network failures.
处理错误 - 数据库和网络本质上是不稳定的,因此您始终必须考虑暂时性错误。EF Core 包含各种功能,可通过在网络故障时重试来保持连接复原能力。 -
Synchronous vs. asynchronous—EF Core provides both synchronous and asynchronous commands for interacting with the database. Often, async is better for web apps, but this argument has nuances that make it impossible to recommend one approach over the other in all situations.
同步与异步 – EF Core 提供用于与数据库交互的同步和异步命令。通常,async 更适合 Web 应用程序,但此参数具有细微差别,因此不可能在所有情况下都推荐一种方法而不是另一种方法。
EF Core is a great tool for being productive in writing data-access code, but some aspects of working with a database are unavoidably awkward. Database management is one of the thorniest problems to tackle. Most web applications use some sort of database, so the following problems are likely to affect ASP.NET Core developers at some point:
EF Core 是高效编写数据访问代码的绝佳工具,但使用数据库的某些方面不可避免地会遇到困难。数据库管理是需要解决的最棘手的问题之一。大多数 Web 应用程序都使用某种类型的数据库,因此以下问题可能会在某个时候影响 ASP.NET Core 开发人员:
-
Automatic migrations—If you deploy your app to production automatically as part of some sort of DevOps pipeline, you’ll inevitably need some way to apply migrations to a database automatically. You can tackle this situation in several ways, such as scripting the .NET tool, applying migrations in your app’s startup code, using EF Core bundles, or using a custom tool. Each approach has its pros and cons.
自动迁移 — 如果您将应用程序作为某种 DevOps 管道的一部分自动部署到生产环境,那么您不可避免地需要某种方法来自动将迁移应用于数据库。您可以通过多种方式解决这种情况,例如为 .NET 工具编写脚本、在应用程序的启动代码中应用迁移、使用 EF Core 捆绑包或使用自定义工具。每种方法都有其优点和缺点。 -
Multiple web hosts—One specific consideration is whether you have multiple web servers hosting your app, all pointing to the same database. If so, applying migrations in your app’s startup code becomes harder, as you must ensure that only one app can migrate the database at a time.
多个 Web 主机 - 一个特别的考虑因素是您是否有多个 Web 服务器托管您的应用程序,所有服务器都指向同一个数据库。如果是这样,在应用程序的启动代码中应用迁移会变得更加困难,因为您必须确保一次只有一个应用程序可以迁移数据库。 -
Making backward-compatible schema changes—A corollary of the multiple-web-host approach is that you’ll often be in a situation in which your app accesses a database that has a newer schema than the app thinks. Normally, you should endeavor to make schema changes backward-compatible wherever possible.
进行向后兼容的架构更改 – 多 Web 主机方法的一个必然结果是,您经常会遇到这样的情况:您的应用程序访问的数据库具有比应用程序认为的架构更新的架构。通常,您应该尽可能努力使架构更改向后兼容。 -
Storing migrations in a different assembly—In this chapter I included all my logic in a single project, but in larger apps, data access is often in a different project from the web app. For apps with this structure, you must use slightly different commands when using .NET CLI or PowerShell cmdlets.
将迁移存储在不同的程序集中 – 在本章中,我将所有逻辑包含在一个项目中,但在较大的应用程序中,数据访问通常与 Web 应用程序位于不同的项目中。对于具有此结构的应用程序,在使用 .NET CLI 或 PowerShell cmdlet 时,必须使用略有不同的命令。 -
Seeding data—When you first create a database, you often want it to have some initial seed data, such as a default user. EF 6.x had a mechanism for seeding data built in, whereas EF Core requires you to seed your database explicitly yourself.
种子设定数据 – 首次创建数据库时,您通常希望它具有一些初始种子数据,例如默认用户。EF 6.x 内置了用于设定数据种子的机制,而 EF Core 要求您自己显式设定数据库种子。
How you choose to handle each of these problems depends on the infrastructure and the deployment approach you take with your app. None is particularly fun to tackle, but all are unfortunate necessities. Take heart, though; all these problems can be solved one way or another!
您选择如何处理这些问题取决于您对应用程序采用的基础设施和部署方法。没有一个是特别有趣的,但都是不幸的必需品。不过,请放心;所有这些问题都可以以某种方式解决!
That brings us to the end of this chapter on EF Core and part 2 of the book. In part 3 we move away from minimal APIs to look at building server-rendered page-based applications with Razor Pages.
这样,我们就结束了本章的 EF Core 和本书的第 2 部分。在第 3 部分中,我们将从最小的 API 转向使用 Razor Pages 构建服务器呈现的基于页面的应用程序。
12.6 Summary
12.6 总结
-
EF Core is an ORM that lets you interact with a database by manipulating standard POCO classes called entities in your application, reducing the amount of SQL and database knowledge you need to be productive.
EF Core 是一种 ORM,允许您通过作应用程序中称为实体的标准 POCO 类来与数据库交互,从而减少提高工作效率所需的 SQL 和数据库知识量。 -
EF Core maps entity classes to tables, properties on the entity to columns in the tables, and instances of entity objects to rows in these tables. Even if you use EF Core to avoid working with a database directly, you need to keep this mapping in mind.
EF Core 将实体类映射到表,将实体的属性映射到表中的列,并将实体对象的实例映射到这些表中的行。即使使用 EF Core 来避免直接使用数据库,也需要牢记此映射。 -
EF Core uses a database-provider model that lets you change the underlying database without changing any of your object manipulation code. EF Core has database providers for Microsoft SQL Server, SQLite, PostgreSQL, MySQL, and many others.
EF Core 使用数据库提供程序模型,该模型允许您在不更改任何对象作代码的情况下更改基础数据库。EF Core 具有适用于 Microsoft SQL Server、SQLite、PostgreSQL、MySQL 等的数据库提供程序。 -
EF Core is cross-platform and has good performance for an ORM, but it has a different feature set from EF 6.x. Nevertheless, EF Core is recommended for all new applications after EF 6.x.
EF Core 是跨平台的,对于 ORM 具有良好的性能,但它的功能集与 EF 6.x 不同。不过,建议将 EF Core 用于 EF 6.x 之后的所有新应用程序。 -
EF Core stores an internal representation of the entities in your application and how they map to the database, based on the DbSet
properties on your application’s DbContext. EF Core builds a model based on the entity classes themselves and any other entities they reference.
EF Core 根据应用程序的 DbContext 上的 DbSet属性存储应用程序中实体的内部表示形式以及它们如何映射到数据库。EF Core 基于实体类本身及其引用的任何其他实体构建模型。 -
You add EF Core to your app by adding a NuGet database provider package. You should also install the design packages for EF Core, which works in conjunction with the .NET tools to generate and apply migrations to a database.
通过添加 NuGet 数据库提供程序包,将 EF Core 添加到应用。您还应该安装 EF Core 的设计包,它与 .NET 工具结合使用,以生成迁移并将其应用于数据库。 -
EF Core includes many conventions for how entities are defined, such as primary keys and foreign keys. You can customize how entities are defined declaratively, by using DataAnnotations, or by using a fluent API.
EF Core 包括许多关于如何定义实体的约定,例如主键和外键。您可以使用 DataAnnotations 或使用 Fluent API 自定义以声明方式定义实体的方式。 -
Your application uses a DbContext to interact with EF Core and the database. You register it with a DI container using AddDbContext
, defining the database provider and providing a connection string. This approach makes your DbContext available in the DI container throughout your app.
应用程序使用 DbContext 与 EF Core 和数据库交互。使用 AddDbContext将其注册到 DI 容器,定义数据库提供程序并提供连接字符串。此方法使您的 DbContext 在整个应用程序中的 DI 容器中可用。 -
EF Core uses migrations to track changes to your entity definitions. They’re used to ensure that your entity definitions, EF Core’s internal model, and the database schema match.
EF Core 使用迁移来跟踪对实体定义的更改。它们用于确保实体定义、EF Core 的内部模型和数据库架构匹配。 -
After changing an entity, you can create a migration using either the .NET tool or Visual Studio PowerShell cmdlets. To create a new migration with the .NET command-line interface, run dotnet ef migrations add NAME in your project folder, where NAME is the name you want to give the migration. This command compares your current DbContext snapshot with the previous version and generates the necessary SQL statements to update your database.
更改实体后,您可以使用 .NET 工具或 Visual Studio PowerShell cmdlet 创建迁移。要使用 .NET 命令行界面创建新的迁移,请在项目文件夹中运行 dotnet ef migrations add NAME,其中 NAME 是要为迁移提供的名称。此命令将当前 DbContext 快照与以前的版本进行比较,并生成必要的 SQL 语句来更新数据库。 -
You can apply the migration to the database by using dotnet ef database update. This command creates the database if it doesn’t already exist and applies any outstanding migrations.
可以使用 dotnet ef database update 将迁移应用于数据库。此命令将创建数据库(如果尚不存在)并应用任何未完成的迁移。 -
EF Core doesn’t interact with the database when it creates migrations—only when you update the database explicitly—so you can still create migrations when you’re offline.
EF Core 在创建迁移时不与数据库交互,仅在显式更新数据库时交互,因此在脱机时仍可以创建迁移。 -
You can add entities to an EF Core database by creating a new entity, e, calling _context.Add(e) on an instance of your application’s data context, _context, and calling _context.SaveChangesAsync(). This technique generates the necessary SQL INSERT statements to add the new rows to the database.
可以通过创建新实体 e 并将实体添加到 EF Core 数据库,调用_上下文。Add(e) 在应用程序的数据上下文的实例上,_context,然后调用_SaveChangesAsync()上下文生成必要的 SQL INSERT 语句以将新行添加到数据库中。 -
You can load records from a database by using the DbSet
properties on your app’s DbContext. These properties expose the IQueryable interface so you can use LINQ statements to filter and transform the data in the database before it’s returned.
您可以通过在应用程序的 DbContext 上使用 DbSet属性从数据库加载记录。这些属性公开 IQueryable 接口,以便您可以在返回数据库中的数据之前使用 LINQ 语句对其进行筛选和转换。 -
Updating an entity consists of three steps: reading the entity from the database, modifying the entity, and saving the changes to the database. EF Core keeps track of which properties have changed so that it can optimize the SQL it generates.
更新实体包括三个步骤:从数据库中读取实体、修改实体以及保存对数据库的更改。EF Core 会跟踪哪些属性已更改,以便可以优化它生成的 SQL。 -
You can delete entities in EF Core by using the Remove method, but you should consider carefully whether you need this function. Often. a soft delete using an IsDeleted flag on entities is safer and easier to implement.
可以使用 Remove 方法删除 EF Core 中的实体,但应仔细考虑是否需要此函数。通常,在实体上使用 IsDeleted 标志的软删除更安全且更易于实现。 -
This chapter covers only a subset of the problems you must consider when using EF Core in your applications. Before using it in a production app, you should consider (among other things) the data types generated for fields, validation, handling concurrency, the seeding of initial data, handling migrations on a running application, and handling migrations in a web-farm scenario.
本章仅介绍在应用程序中使用 EF Core 时必须考虑的问题的子集。在生产应用程序中使用它之前,您应该考虑(除其他事项外)为字段生成的数据类型、验证、处理并发、初始数据的种子设定、在正在运行的应用程序上处理迁移以及在 Web 场方案中处理迁移。