EF Core Sidekick 进阶指南
简介
EF Core Sidekick 是一款能够加速 Web API 开发的工具,它具备自动逆向工程和脚手架功能,使开发人员无需为每个实体/服务/控制器手动编写重复的样板代码,从而可以专注于 Web API 开发的关键部分,例如业务逻辑和自定义行为。
本教程将以 EF Core Sidekick 快速入门指南 为基础,深入探讨 EF Core Sidekick 的高级主题,重点解释代码生成后实体的架构和结构、如何自定义实体以及长期维护的最佳方法。
目标读者
- 已了解 C# 和 EF Core 基础知识,但希望优化 Web API 开发流程和效率的开发人员。
- 已熟悉使用 EF Core Sidekick 创建 Web API 的开发人员。
- 希望更好地了解 EF Core Sidekick 流程及其对象布局,并希望在实际项目的维护生命周期中继续使用它的开发人员。
先决条件
熟悉 C# 和 EF Core
已安装 Visual Studio 2022
已安装 EF Core Sidekick
已安装 SQL Server
Microsoft 的 School 示例数据库
启动教程的项目 – 在此处下载
(可选) 完成教程后的最终项目 – 在此处下载
理解生成的代码结构
以下是从数据库对象生成不同类型实体的结果文件:
注意:假设使用默认生成配置文件
从数据库表/视图生成实体
- Contract:包含实体模型定义。
- Dal(数据访问层):包含将模型映射到数据库的类,包括 DataContext 和使用 EF Core 配置实体的类。
导入存储过程
- Contract > Models:描述过程执行结果的模型。
- Dal > IDataContextProcedures:在接口中定义导入过程的签名。
- Dal > DataContext.Procedures:实现过程调用代码并将其添加到 DataContext 代码中(通过部分类)。
- Dal > ProceduresExtensions:过程调用代码使用的辅助方法。
导入函数
- Dal > DataContext.Functions:包含使用初始化的 DbFunction 特性注释的函数的方法签名。
这样就可以在 LINQ 查询中使用该函数了。
从模型生成服务和 API
在实体上使用 生成服务和 API 功能时,将创建以下对象:
Common:包含允许分页结果的辅助类
- Repository:用于 Repository/工作单元模式的接口
- Security:用于请求身份验证的用户接口
Contract:
- IEntityServiceBase:构成所有服务基础的接口。定义数据访问/更新方法。
- IPersonService:我们所选模型的服务接口。
Controllers:包含使用服务响应传入请求的 Controller 类。这是处理分页的地方。
Dal > Repository:在 Common/Repository 上定义的 Repository/工作单元模式的实现。
Security:包含 ICurrentUser 的默认实现以及用于将该服务注册为管道中单例的扩展方法。
Service:Contract 中定义的接口的实现,以及用于将服务注册到管道的扩展方法。
Validation:属性定义、模型验证服务以及用于将验证服务注册到管道的扩展方法。
代码生成配置
从数据库实体生成代码时,可以通过配置修改输出对象的生成方式。
配置文件设置
生成实体后,您将看到以下窗口:
在此窗口中,您可以配置一些与代码输出相关的设置:
设置 | 功能 |
---|---|
生成配置 | 允许您选择和配置多个包含此页面不同配置集的配置文件。 |
覆盖文件 | 覆盖之前生成的现有文件。这将导致您写入生成对象的任何自定义代码丢失。 |
将自定义代码应用到生成的代码 | 将 自定义代码 选项卡中配置的代码应用于输出。如果未设置此选项,自定义代码将不会反映在生成的文件中。 |
启用实体命名规则 | 以预设置的规则来命名实体文件。 |
为每个架构创建存储实体文件的字文件夹 | 创建一个以架构名称命名的子文件夹,用于存放生成的对象。 |
启用实体更改检测与通知 | 设置实体以跟踪其属性的更改,以便将其自动保存回数据库。 |
目标配置 | 定义放置不同输出组件的目标目录。这些位置在默认配置文件中无法更改。 |
自定义代码
自定义代码 用于配置自定义代码段及其在输出文件中的插入位置:
- 命名空间:如果您使用的自定义代码依赖于其他命名空间中的类,则这些类将导入到此处。
- 继承:配置要从这些类继承的对象。在其他类上定义自定义行为时,可以在此处将其集成到输出实体中。
- 类特性:使用此处定义的特性修饰生成的类。
- 属性特性:使用此处定义的特性修饰生成的类中的属性。
- 代码片段:要添加到生成的对象的自定义代码。这些代码片段可以插入到文件、类或方法的开头/结尾。
对于所有这些类型的自定义代码,都可以配置作用域;也就是说,它可以应用于服务接口/实现、实体或控制器。
自定义生成的代码
仅使用 EF Core Sidekick 生成的代码,项目进展有限。不可避免地,需要通过自定义代码以添加专门的业务逻辑,或使用特定于领域的行为扩展功能。EF Core Sidekick 生成的输出完全可以支持这一点,因为它的结构良好,遵循关注点分离的生成方法。但是,如果数据库对象发生更改,则需要做出以下决定:
a) 通过更改实体和配置方法手动将更改实现到代码中(不会丢失任何自定义代码)。
b) 从数据库对象重新导入实体(无需手动编写代码,但自定义代码可能会全部丢失)。
因此,EF Core Sidekick 允许您配置自定义代码片段,这些代码片段会在每次生成对象时注入到生成的对象中。通过这种方法,您可以高效地处理数据库对象中的更改,而不会丢失自定义代码。
重新生成模型时管理自定义代码
为了在实体上启用软删除,我们将在 Contract 文件夹中添加一个名为 ISoftDelete 的新接口,其内容如下:
然后,像之前一样打开 从数据库创建实体 窗口,选择 Person 实体并点击 下一步。
在下一个窗口中,切换到 自定义代码 选项卡并配置以下条目:
注意:我们添加了 [NotMapped]
特性,因为该属性不在数据库中,仅用于演示。
编辑 代码片段 条目时,您可以输入多行代码。对象生成后,注入的代码也会保持多行。
返回 生成设置 选项卡,并确保 覆盖文件 和 将自定义代码应用到生成的代码 复选框已勾选。
注意:本教程的其余部分将假定这些复选框已启用。
然后点击 完成。
如果我们打开生成的实体文件 Person.cs,我们将看到自定义代码已插入到类中:
使用自定义逻辑增强服务
让我们通过添加一个可供多个服务重用的过滤功能来扩展项目。
右键单击 Person.cs 实体,然后选择 EF Core Sidekick > 生成服务和API。选择之前选过的相同服务(Get、GetList 和 GetPage),然后点击 下一步。然后,在 自定义代码 选项卡中,我们将以代码片段的形式向服务添加一个过滤函数。
首先在接口中:
public IQueryable<TEntity> GetFiltered<TEntity>(Expression<Func<TEntity, bool>> filter, CancellationToken token = default)
where TEntity : class;
然后在实现中:
public IQueryable<TEntity> GetFiltered<TEntity>(Expression<Func<TEntity, bool>> filter, CancellationToken token = default)
where TEntity : class
{
return _unitOfWork.GetRepository<TEntity>().GetQueryable(false)
.Where(filter);
}
代码片段将如下所示:
请注意,代码以多行形式输入:
我们还将添加一个导入语句来提供 Expression
对象:
然后点击 完成。
这样,我们就可以在所需的服务上实现自定义过滤器。例如,要向 PersonService 添加 First Name 过滤器,我们可以手动修改 IPersonService.cs 文件并添加以下内容:
然后在 PersonService.cs 文件中添加:
最后,在 PersonController.cs 文件中添加端点:
注意:如果您在设置了 覆盖文件 选项的情况下重新生成服务/API,这些更改将会丢失。
这样,我们就以持久化的方式向基础服务添加了功能,同时对生成的服务和 API 进行了更具体的更改。
扩展 API 功能
您还可以手动修改生成的 API 控制器的功能(更改可能会丢失),也可以通过 自定义代码 进行修改。
包装 API 响应
让我们修改控制器,使响应始终包装在预定义的对象中。我们将在项目根目录下创建一个名为 Filters 的目录,并在其中创建一个 ResponseWrapperFilterAttribute.cs 对象,其定义如下:
接下来,我们将使用我们创建的这个特性来装饰控制器:
或者,您也可以通过在自定义代码设置中指定此特性,将其注入到代码生成过程中(以便将其包含在生成过程中):
运行 Web API,并在 Swagger 页面上测试任何端点,您将获得类似以下结果:
您可以采用类似的方法修改其他 API 层,例如自定义异常管理、自定义安全策略等。
有效处理数据库架构变更
如果您在模型生成后未对生成的文件进行任何更改,则处理数据库变更非常简单,只需再次执行该过程即可(确保已设置覆盖选项)。
但是,随着您使用 Web API 的经验越来越丰富,您可能需要手动更改生成的模型或服务。在这种情况下,您可能不希望在处理数据库修改时丢失这些更改。对于这些情况,您可以通过生成窗口的 自定义代码 功能进行自定义更改。通过此功能,您可以放心,即使修改了数据库架构本身,这些更改在重新生成后仍将保留。
但是,在某些情况下,您可能无法依赖此功能,或者更改过于复杂,无法使用生成过程中的 自定义代码 功能。对于这些情况,由于 EF Core Sidekick 以 EF Core 为核心,您只需在指定的位置进行更改即可适应更改,即 Contract/Entities 用于表的更改,以及 Dal/Configurations 用于配置实体的字段:
此外,您还可以在单独的类中定义自定义逻辑,然后在生成过程中通过 自定义代码 选项卡将此逻辑插入到生成的实体中。
使用其他数据源
对于更复杂的系统,不仅从常规表中检索数据;也经常从视图、函数和过程中检索数据。幸运的是,EF Core Sidekick 也支持从这些类型的数据源检索中创建实体。
视图
与创建其他类型的实体过程相同:右键单击 项目 > EF Core Sidekick > 从DB创建实体。但这次我们选择数据库中的视图对象。
注意:该视图未包含在 School 数据库示例中,您需要在 School 架构中创建它:
CREATE VIEW dbo.InstructorOffices
AS SELECT p.PersonID as InstructorID, p.FirstName, p.LastName, o.Location
FROM Person p
INNER JOIN OfficeAssignment o on p.PersonID = o.InstructorID
请注意,您可以更改生成的属性的名称:
映射将自动处理:
然后,在尝试生成服务和 Web API 时,EF Core Sidekick 会检测到实体引用了视图,并且它仅预配置了只读端点:
函数
注意:该函数未包含在 School 数据库示例中,您需要在 School 架构中创建它:
CREATE OR ALTER FUNCTION uf_GetHighestGradedCourse
(@studentId int)
RETURNS int
BEGIN
RETURN (SELECT TOP(1) CourseID FROM StudentGrade WHERE StudentID = @studentId
ORDER BY StudentGrade.Grade DESC);
END
导入数据库函数时,流程相同,但生成的对象略有不同。
您可以在 定义 选项卡中查看该函数的定义:
结果位于文件 Dal > DataContext.Functions.cs 中:
生成的对象有所不同,因为您无法从中生成服务或 Web API,由于该函数已经使用 DbFunctionAttribute 修饰,这意味着我们现在可以在 LINQ 查询中使用它。
我们在 PersonService 中添加一个新函数:
然后使用 Visual Studio 的重构工具快速将该方法添加到接口中。将光标放在函数名称上,按下“Alt + .”:
这会将方法签名放置在 IPersonService 接口中:
现在我们可以使用此服务在 PersonController 中创建一个新的端点:
如果我们运行 Web API 并使用 Swagger 接口调用端点,我们将在控制台窗口输出中看到正在使用的函数:
存储过程
从数据库对象创建新实体时,请选择要从中生成实体的过程:
与函数一样,您可以在 定义 选项卡上查看过程的定义。
生成完成后,将创建以下文件:
这些文件具有以下用途:
DboInsertPersonResult.cs -- 定义包含过程返回数据的模型。
IDataContextProcedures.cs -- 此接口定义了存储过程的签名,该接口将由下一个文件实现。
DataContext.Procedures -- 实现先前定义的接口,是调用存储过程的具体类。
接下来,我们需要将返回类型对象 DboInsertPersonResult 作为模型注册到 DataContext 中。我们通过修改 DataContext 的 OnModelCreating 来实现:
请注意,存储过程和函数有时可能会定义输出参数,这些参数需要由服务处理(REST API 调用中没有输出参数)。在本例中,我们不需要使用输出参数,因此在服务实现中将其丢弃。我们将对 PersonService 类进行以下更改:
然后,我们将该方法拉入接口,就像我们操作函数那样。
服务更新后,我们可以使用 EF Core Sidekick 重新生成新方法的控制器。右键单击 IPersonService.cs 文件,然后选择 EF Core Sidekick > 生成API:
在配置窗口中,我们选择已创建的方法和只读方法:
注意:方法发现包含基类,基类包含所有显示的方法,以及我们在接口中看到的方法。
点击 下一步 和 完成 按钮。最后,我们再次运行 Web API,并在 Swagger 页面中看到新的端点:
现在我们可以使用它了:
实施关键 API 安全实践
在 EF Core Sidekick 生成的 API 中使用 JWT 进行身份验证
确保 Web API 暴露的资源只能被获得授权的人员访问,这是 API 开发中非常重要的一环。幸运的是,ASP.NET Core 在提供的安全方法方面非常灵活。最常用的安全技术之一是使用 JWT 令牌进行端点授权。
JWT 身份验证
您可以点击此处了解更多关于 ASP.NET Core 中 JWT 身份验证的信息。简单来说,如果您已设置了身份验证服务器,那么需要完成的操作如下:
- 在 Program.cs 中配置 JWT:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://the-authentication-server";
options.Audience = "the-audience";
});
app.UseAuthentication();
app.UseAuthorization();
- 保护端点:
[Authorize]
[HttpGet("secure-data")]
public ActionResult<string> GetSecureData()
{
return "This is protected data.";
}
这样,只有被身份验证服务器授予了令牌的人员才能访问 GetSecureData()
中的资源。
在控制器和端点级别配置授权
使用 EF Core Sidekick,您可以选择在 a) 控制器级别或 b) 端点级别设置授权策略,方法是在相应字段中选择相应的策略:
可维护性和性能的最佳实践
EF Core Sidekick 帮助您快速生成符合最佳实践的可维护代码。例如:
- 实现高效简洁的设计模式,以提高可维护性和隔离性(例如,存储库和工作单元模式)。
- 通过将特定功能隔离在各自的类中,清晰地分离关注点,从而提高可重用性。
但是,对于实际项目,在手动维护期间,您也需要注意以下几点:
- 尽量减少手动代码更改,尽可能使用 EF Core Sidekick 的自定义代码功能。
- 将敏感值/字符串存储在源代码之外。在本例中,我们使用了用户机密信息,但这主要只适用于开发环境。您应该使用其他安全获取敏感信息(密码、连接字符串等)的方法,例如 Azure Key Vault。
以下是一些需要牢记的事项。
保持适当的关注点和职责分离
使用 EF Core Sidekick 生成实体、服务或控制器时,生成的文件会根据其在架构中的位置放置在不同的位置和命名空间中:
为了保持高度的可维护性,建议按照此结构进行手动更改。或者,如果您的组织有特定的结构化方式,您可以在生成窗口的 生成配置 中创建一个符合这些规范的配置文件。
实现 DTO 而不是直接返回实体
使用 Web API 的数据时,从数据库返回整个实体更快捷、更简单,但并非总是如此,比如说:
- 实体包含了许多属性,在很多情况下并没有使用(例如,有时我们只需要 Person 的 ID 和名称,而不关心其他属性)。
- 实体包含一些敏感属性,不宜向没有授权的用户公开。
对于这些情况,最好实现 DTO(数据传输对象),复制实体的某些值(尽量只复制完成特定请求所需的最少数据)。
这个过程可能需要重复执行,因此 EF Core Sidekick 还提供了一项功能,可以通过右键单击 实体 > EF Core Sidekick > 生成 DTO 来快速从现有实体设计 DTO:
点击 下一步 后,您可以选择是否同时生成内置转换器,这样就无需手动执行此操作:
生成 DTO 后,您可以使用生成的内置转换器将它们集成到控制器中:
这样就可以轻松创建 DTO。如果您选择生成内置转换器,您可以通过函数调用在实体和 DTO 之间进行转换。
总结与后续
阅读完以上内容,希望您能够了解创建最简单的 Web API 所需的工作量,以及 EF Core Sidekick 带来的优势。您使用的数据库表越多、越复杂,EF Core Sidekick 为您节省的时间就越显著。而且,使用该工具并不会限制您的操作,因为您从表中生成了 C# 类后,您可以进行任何您认为合适的修改;或者,如果您发现自己需要不断重新生成模型,您可以将自定义代码添加到生成设置中,以便每次运行该过程时自动注入这些代码。