使用实体框架创建 ASP .NET Core 站点

2025-06-07

使用实体框架创建 ASP .NET Core 站点

学习目标

在本教程中,我们将使用 MVC、Entity Framework 和 RESTful Web API 创建一个 ASP .NET Core 3.0 Web 应用程序。

在本文结束时,我们将拥有一个可操作的 Web 应用程序,它允许我们创建和修改测试套件并在每个测试套件中添加管理测试用例。

我们将专注于快速启动和运行,并将主要采用新 Web 应用程序的默认外观和感觉。

在此过程中我们将讨论:

  • ASP.NET核心3.0
  • 模型视图控制器 (MVC)
  • 实体框架
  • Web API

了解 ASP .NET Core 3.0 MVC Web 应用程序

ASP .NET Core 3.0 是一款运行在 .NET Core 3.0 上的 Web 服务器。它是一款企业级 Web 服务器,能够提供静态或动态网页以及 API 端点。

ASP .NET 提供了多种网页技术,但在本文中,我们将重点介绍其带有 Razor 语法的模型视图控制器 (MVC)。

这不是一篇关于了解 MVC 或 Razor 细节的文章,但我们将查看一些与这些内容相关的代码。

模型视图控制器由以下组件组成:

  • 包含数据的模型或类
  • 呈现数据的视图或网页
  • 控制器是一个处理来自视图的请求、修改模型并指向更新的视图的类。

Razor Views 使用基于 HTML 构建的模板引擎,支持各种指令。本文将重点介绍HTML 助手,并利用 Visual Studio 的脚手架功能生成其余视图。

我们将要使用的另一项技术是实体框架,它是数据库的 .NET 包装器,可以处理动态查询和数据库迁移。

让我们开始吧。

创建项目

在 Visual Studio 2019 中,创建一个新项目并选择一个 ASP.NET Core Web 应用程序项目。

创建新项目对话框,其中选中“ASP .NET Core Web 应用程序”

ASP .NET 有很多不同的风格,但在本教程中,我们将使用 ASP .NET Core 3.0 Web 应用程序(模型-视图-控制器)应用程序。

创建新的 ASP.NET Core Web 应用程序对话框,并选择 Web 应用程序(模型-视图-控制器)

在创建应用程序之前,单击Change“身份验证”,然后选择“个人用户帐户”,再选择“在应用程序中存储用户帐户”:

已选择在应用内存储用户帐户的个人用户帐户

虽然我们不会对身份验证或身份做太多的工作,但这将为我们在本文中讨论实体框架做好准备。

ASP .NET Core 在您刚开始设置时,会默认使用开发机器上的本地数据库。这让您可以快速开始编写代码,并在可移植的环境中工作,直到您准备好切换到其他数据库服务器为止。

继续创建项目。

启动应用程序

点击Control + F5或打开Debug菜单并选择Start without Debugging。应用程序将开始编译并立即启动。

您可能会看到类似以下内容的对话框:

SSL 警告对话框

我建议您点击“是”,然后信任您收到的证书。这样您就可以在本地使用 https 了。

应用程序编译后,您的浏览器就会打开,并且您应该会看到类似以下内容:

ASP .NET Core 3.0 MVC Web 应用程序已启动并运行

恭喜,您有一个可以运行的 ASP .NET Core Web 应用程序。


接下来,点击“登录”来创建/验证您的用户。您将看到一个数据库错误页面,通知您需要进行迁移。

为了解决此错误,我们需要前往程序包管理器控制台,输入以下内容Update-Database并按回车键。这将导致本地数据库使用身份提供者指定的表进行更新。

运行 Update-Database 的程序包管理器控制台

一旦完成后,您将能够成功创建和验证您的用户和登录。

添加第一个实体

首先,让我们在Models文件夹中创建一个类。它可以是任何你喜欢的类型,但在本例中,我们将其命名为TestSuite。这TestSuite是一个容器,TestCase稍后将用于存放实体。

我的TestCase班级是这样的:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace MattEland.TestManager.Models
{
[Display(Name = "Test Suite")]
public class TestSuite
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[HiddenInput(DisplayValue = true)]
[Display(Name = "Created")]
public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow;
[HiddenInput(DisplayValue = true)]
[Display(Name = "Last Modified")]
public DateTime DateModifiedUtc { get; set; } = DateTime.UtcNow;
}
}
view raw TestSuite.cs hosted with ❤ by GitHub
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace MattEland.TestManager.Models
{
[Display(Name = "Test Suite")]
public class TestSuite
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[HiddenInput(DisplayValue = true)]
[Display(Name = "Created")]
public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow;
[HiddenInput(DisplayValue = true)]
[Display(Name = "Last Modified")]
public DateTime DateModifiedUtc { get; set; } = DateTime.UtcNow;
}
}
view raw TestSuite.cs hosted with ❤ by GitHub

注意属性的大量使用。让我们来回顾一下:

  • Key告诉实体框架这是数据库表中的主键/标识列。
  • Required表示该字段必须有值。客户端和 Entity Framework 生成的列定义都会对此进行解释。
  • HiddenInputMVC 模板引擎会读取该值,从而不渲染指定字段的输入。下一步我们将了解这意味着什么。
  • Display或者DisplayName可用于自定义模板中向用户呈现字段的方式。

接下来我们将添加一个 MVC 控制器来处理我们的实体并向用户呈现视图。

我们将通过右键单击解决方案资源管理器并选择添加>控制器...来执行此操作。

这将打开如下图所示的“添加新脚手架项目”对话框:

使用选定的实体框架,添加带有视图的 MVC 控制器的新脚手架项

搭建基架的过程会让 Visual Studio 根据您的选项和引用的代码生成代码。在我们的示例中,它将根据我们指定的模型以及“添加 MVC 控制器”对话框中的选项生成字段。

我们将TestSuite在“模型类”字段中指定实体。对于“数据上下文”,您可以根据需要指定一个新的类,但使用现有类也没什么不妥ApplicationDbContext。其余复选框最好保持选中状态。

添加 MVC 控制器对话框,并将 TestSuite 指定为模型类

点击“添加”后,系统会为您生成所有内容。这可能需要一些时间。

深入了解实体框架迁移

脚手架操作完成后,查看一下ApplicationDbContext。脚手架操作为我们在上下文中添加了一行:

public DbSet<TestSuite> TestSuite {get; set;}

因为我们现在在 EF 上下文中定义了一个新实体,所以我们需要为定义的实体添加一个实体框架迁移。

通过在包管理器控制台中运行来执行此操作Add-Migration AddedTestCases。请注意,迁移的名称可以是任何您想要的名称,只要您能轻松理解它所代表的操作即可。

完成后,查看Add-Migration操作生成的类。它应该看起来像这样:

using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace MattEland.TestManager.Data.Migrations
{
public partial class AddedTestSuite : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TestSuite",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(nullable: false),
Description = table.Column<string>(nullable: true),
DateCreatedUtc = table.Column<DateTime>(nullable: false),
DateModifiedUtc = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TestSuite", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TestSuite");
}
}
}
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace MattEland.TestManager.Data.Migrations
{
public partial class AddedTestSuite : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TestSuite",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(nullable: false),
Description = table.Column<string>(nullable: true),
DateCreatedUtc = table.Column<DateTime>(nullable: false),
DateModifiedUtc = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TestSuite", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TestSuite");
}
}
}

这有助于揭开数据库迁移过程的神秘面纱。数据库迁移只是在应用或恢复更改时执行的代码。

准备好应用此数据库迁移后,转到程序包管理器控制台并运行Update-Database

展现新观点

在第 25 行左右_layout.cshtml添加以下代码:

请注意,asp-controller这里指的是控制器的名称,该名称是在本教程前面搭建脚手架时命名的。

此链接将允许我们导航到新的一系列视图。您应该能够创建、读取、更新和删除TestSuite实体。

测试套件列表

虽然生成的代码基本上“能用”,但我确实需要做一些调整。具体来说,在创建页面上,我删除了“创建”和“修改”字段。

此外,在创建和编辑帖子方法中,TestSuitesController我还设置了初始创建时间并在编辑时更新了修改时间。

理想情况下,编辑帖子处理程序不应接收对象的所有属性,而应仅复制用户界面中公开的值。这可以防止某些类型的攻击,并且如果实体在页面加载后发生更改,也能更好地实现并发。

添加第二个实体

现在我们已经有一个运行良好的实体,让我们为每个实体添加一个TestCase实体和TestCases集合TestSuite。这代表可以执行的特定测试。

首先,让我们创建一个简单的枚举来表示测试用例的状态:

public enum TestStatus
{
[Display(Name = "Not Run")]
NotRun,
Passing,
Failed,
Ignored
}
view raw TestStatus.cs hosted with ❤ by GitHub
public enum TestStatus
{
[Display(Name = "Not Run")]
NotRun,
Passing,
Failed,
Ignored
}
view raw TestStatus.cs hosted with ❤ by GitHub

在这里,我使用Display属性来指定NotRun如果该值在用户界面中呈现时应如何显示。

接下来,让我们定义TestCase

public class TestCase
{
[Key]
public int Id { get; set; }
[Required]
[DisplayName("Test Suite")]
[ForeignKey("TestSuite")]
public int TestSuiteId { get; set; }
[Display(Name = "Test Suite")]
public TestSuite TestSuite { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[HiddenInput(DisplayValue = true)]
[Display(Name = "Created")]
public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow;
[HiddenInput(DisplayValue = true)]
[Display(Name = "Last Modified")]
public DateTime DateModifiedUtc { get; set; } = DateTime.UtcNow;
public TestStatus Status { get; set; } = TestStatus.NotRun;
}
view raw TestCase.cs hosted with ❤ by GitHub
public class TestCase
{
[Key]
public int Id { get; set; }
[Required]
[DisplayName("Test Suite")]
[ForeignKey("TestSuite")]
public int TestSuiteId { get; set; }
[Display(Name = "Test Suite")]
public TestSuite TestSuite { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[HiddenInput(DisplayValue = true)]
[Display(Name = "Created")]
public DateTime DateCreatedUtc { get; set; } = DateTime.UtcNow;
[HiddenInput(DisplayValue = true)]
[Display(Name = "Last Modified")]
public DateTime DateModifiedUtc { get; set; } = DateTime.UtcNow;
public TestStatus Status { get; set; } = TestStatus.NotRun;
}
view raw TestCase.cs hosted with ❤ by GitHub

这里的字段与之前的大致相同,但请注意TestSuiteTestSuiteId。是外键关系字段,用于存储属性引用的实体TestSuiteId的键TestSuiteTestSuite

我们还将向该类添加一个集合,TestSuite以便我们可以获取TestCase与其关联的实体:

public ICollection<TestCase> TestCases {get; set;}
view raw TestCases.cs hosted with ❤ by GitHub
public ICollection<TestCase> TestCases {get; set;}
view raw TestCases.cs hosted with ❤ by GitHub

从这里开始,我们可以继续TestCase按照我们之前所做的同样的方式构建实体TestSuite

添加 MVC 控制器对话框,并将 TestCase 指定为模型类

一旦脚手架操作完成,我们就可以使用Add-Migration AddedTestCases包管理器控制台创建数据库迁移。

查看生成的迁移以及它如何处理外键。

自动迁移数据库

不过,这次我们不要运行Update-Database,而是看看如何让您的应用程序在启动时自动迁移。

转到Startup.cs并粘贴以下方法:

private static void ApplyDatabaseMigrations(IApplicationBuilder app)
{
using var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope();
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
}
private static void ApplyDatabaseMigrations(IApplicationBuilder app)
{
using var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope();
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
}

接下来,在方法的底部Configure添加以下语句:

ApplyDatabaseMigrations(app);

当服务器启动时,它将查看数据库并执行尚未运行的任何迁移。

请注意,如果您要在生产应用程序中尝试此功能,建议添加更多日志记录和错误处理。

查看测试用例的工作

TestCasesController像以前一样,添加一个指向from的链接Layout.cshtml。这将让我们查看测试用例的默认视图。

我首先注意到的是测试用例状态下拉列表无法正常工作。不知何故,默认脚手架似乎无法很好地处理枚举。幸好,有一个简单的方法可以解决这个问题。

使用该select元素,我们可以自定义其asp-items属性。这让我们可以选择下拉菜单中项目的来源。内置的GetEnumSelectListHTML 助手正好能满足我们的需要:

<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<select asp-for="Status" class="form-control" asp-items="Html.GetEnumSelectList<TestStatus>()"></select>
<span asp-validation-for="Status" class="text-danger"></span>
</div>
view raw TestCase.cshtml hosted with ❤ by GitHub
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<select asp-for="Status" class="form-control" asp-items="Html.GetEnumSelectList<TestStatus>()"></select>
<span asp-validation-for="Status" class="text-danger"></span>
</div>
view raw TestCase.cshtml hosted with ❤ by GitHub

这也将尊重我们的显示名称属性并显示“Not Run”而不是“NotRun”。

TestCase 编辑对话框,状态显示选项


@Html.DisplayFor接下来,让我们通过将网格中的替换为类似以下内容,从测试用例列表链接到测试套件ActionFor

这会告诉 ASP .NET 生成指向 TestSuites 控制器上的 Details 操作的链接,并传入要查看的套件的 ID。


最后,让我们通过添加以下代码从测试套件的详细信息视图中呈现测试用例列表:

<div>
<h2>Test Cases</h2>
<ul>
@foreach (var item in Model.TestCases)
{
<li><a asp-action="Details" asp-controller="TestCases" asp-route-id="@item.Id">@item.Name</a></li>
}
</ul>
</div>
<div>
<h2>Test Cases</h2>
<ul>
@foreach (var item in Model.TestCases)
{
<li><a asp-action="Details" asp-controller="TestCases" asp-route-id="@item.Id">@item.Name</a></li>
}
</ul>
</div>

这是非常简单的代码,但它有一个问题——当页面加载时它会得到一个空引用异常。

这是怎么回事?

默认情况下,Entity Framework 不会延迟加载集合,除非我们设置。因此, 为Model.TestCases空,无法进行迭代。

我们可以通过进入 方法来解决这个问题TestSuitesControllerDetails在那里,将 LINQ 语法改为:

var testSuite = await _context.TestSuite
.Include(m => m.TestCases)
.FirstOrDefaultAsync(m => m.Id == id);
view raw TestCaseLinq.cs hosted with ❤ by GitHub
var testSuite = await _context.TestSuite
.Include(m => m.TestCases)
.FirstOrDefaultAsync(m => m.Id == id);
view raw TestCaseLinq.cs hosted with ❤ by GitHub

第 2 行尤其重要。Include告诉实体框架在加载时检索实体TestCasesTestSuite

经过此更改,代码现在应该可以正常工作。

添加 Web API

好了,现在我们有了实体和用户界面,接下来添加一个 Web API 控制器。我们将通过添加控制器对话框来添加,但请选择“带有操作”的 API 控制器,并使用 Entity Framework。

使用选定的实体框架,添加带有 API 控制器和操作的新脚手架项目对话框。

选择TestCase实体并命名控制器TestsController。此名称将确保控制器名称不会与 TestSuite 实体的 MVC 控制器名称重叠。此外,此 API 控制器的默认路径也将api/tests以其名称命名,这听起来很合理。

虽然生成的 API 方法可以让您开箱即用地做很多事情,但让我们添加一些自定义端点来了解如何使用实体框架进行查询。

将以下代码添加到您的TestsController

[HttpGet]
public async Task<ActionResult<IEnumerable<TestCase>>> GetTestCase()
{
return await _context.TestCase.ToListAsync();
}
[HttpGet("passing")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetPassingTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.Passing);
}
[HttpGet("failed")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetFailedTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.Failed);
}
[HttpGet("pending")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetPendingTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.NotRun);
}
private async Task<List<TestCase>> GetTestCasesByStatusAsync(TestStatus status)
{
return await _context.TestCase.Where(t => t.Status == status).ToListAsync();
}
[HttpGet]
public async Task<ActionResult<IEnumerable<TestCase>>> GetTestCase()
{
return await _context.TestCase.ToListAsync();
}
[HttpGet("passing")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetPassingTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.Passing);
}
[HttpGet("failed")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetFailedTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.Failed);
}
[HttpGet("pending")]
public async Task<ActionResult<IEnumerable<TestCase>>> GetPendingTestCases()
{
return await GetTestCasesByStatusAsync(TestStatus.NotRun);
}
private async Task<List<TestCase>> GetTestCasesByStatusAsync(TestStatus status)
{
return await _context.TestCase.Where(t => t.Status == status).ToListAsync();
}

这段代码定义了四个新的 GET 端点来处理请求。每个端点都会调用GetTestCasesByStatusAsync并请求过滤到特定的状态。

然后,该GetTestCasesByStatus方法将使用 LINQ 的方法根据传入的参数Where过滤查询中的实体。TestCasestatus

这是一个使用 Entity Framework 管理数据库对象而无需任何 SQL 的示例。其结果透明、易于维护,并支持 Visual Studio 中的所有代码补全和编译器安全功能。

当 Web 应用程序运行时,我们可以通过使用Postman或类似于我们的一个新端点发出 HTTP GET 请求来测试这一点。

POSTMAN 演示了对新端点的成功 HTTP GET 调用

注意:如果您想了解有关 Web API 的更多信息或如何创建仅 API 项目,请查看我关于 .NET Core API 的专门文章

结论

我们设法启动并运行了一个 ASP .NET Core 3.0 Web 应用程序,它具有用户管理、数据库支持和 Web API。

正如您所见,由属性驱动的脚手架和 HTML 帮助程序是使用户界面可操作的一种非常强大的方式。

此外,Entity Framework 让您能够快速灵活地构建数据库应用程序。它允许您在 Visual Studio 中借助编译器和 IntelliSense 进行操作。这最大限度地降低了您犯错的风险。

实体框架迁移和自动迁移通过将数据库迁移过程嵌入到应用程序本身中,使迁移过程变得极其简单。如果有适当的测试和日志记录以及标准的数据库备份和灾难恢复计划支持,这对于团队来说是一个不错的选择。


最终,ASP .NET Core 显著降低了创建新应用程序的门槛,并为您提供了快速启动和运行的生产力工具。它还具备深度和可定制性,可以从最初的概念发展到每天服务成千上万的客户。

使用实体框架创建 ASP .NET Core 站点一文首先出现在Kill All Defects上。

文章来源:https://dev.to/integerman/create-an-asp-net-core-site-with-entity-framework-5fi4
PREV
C# 8 如何帮助提高软件质量
NEXT
博客您的学习编码之旅入门撰写您的第一篇文章在 Dev.to 上撰写文章推广您的文章下一步做什么?