将模型添加到 ASP.NET Core MVC 应用Add a model to an ASP.NET Core MVC app

本文内容

作者:Rick AndersonTom Dykstra

在本部分中将添加用于管理数据库中的电影的类。这些类将是 MVC 应用的“Model”部分 。

可以结合 Entity Framework Core (EF Core) 使用这些类来处理数据库。EF Core 是对象关系映射 (ORM) 框架,可以简化需要编写的数据访问代码。

要创建的模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系 。它们只定义将存储在数据库中的数据的属性。

在本教程中,首先要编写模型类,然后 EF Core 将创建数据库。

添加数据模型类Add a data model class

右键单击 Models 文件夹,然后单击“添加” > “类” 。将文件命名为 Movie.cs 。

将名为 Movie.cs 的文件添加到“Models”文件夹 。

右键单击 Models 文件夹,然后单击“添加” > “新类” > “空类”。 将文件命名为 Movie.cs 。

使用以下代码更新 Movie.cs 文件 :

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. namespace MvcMovie.Models
  4. {
  5. public class Movie
  6. {
  7. public int Id { get; set; }
  8. public string Title { get; set; }
  9. [DataType(DataType.Date)]
  10. public DateTime ReleaseDate { get; set; }
  11. public string Genre { get; set; }
  12. public decimal Price { get; set; }
  13. }
  14. }

Movie 类包含一个 Id 字段,数据需要该字段作为主键。

ReleaseDate 上的 DataType 特性指定了数据的类型 (Date)。通过此特性:

  • 用户无需在数据字段中输入时间信息。
  • 仅显示日期,而非时间信息。

DataAnnotations 会在后续教程中介绍。

添加 NuGet 包Add NuGet packages

从“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台”(PMC)。

PMC 菜单

在 PMC 中运行以下命令:

  1. Install-Package Microsoft.EntityFrameworkCore.SqlServer

前面的命令添加 EF Core SQL Server 提供程序。提供程序包将 EF Core 包作为依赖项进行安装。在本教程后面的基架步骤中会自动安装其他包。

运行以下 .NET Core CLI 命令:

  1. dotnet tool install --global dotnet-ef
  2. dotnet tool install --global dotnet-aspnet-codegenerator
  3. dotnet add package Microsoft.EntityFrameworkCore.SQLite
  4. dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
  5. dotnet add package Microsoft.EntityFrameworkCore.Design
  6. dotnet add package Microsoft.EntityFrameworkCore.SqlServer

上述命令添加:

  • aspnet-codegenerator 基架工具
  • 适用于 .NET Core CLI 的 Entity Framework Core 工具。
  • EF Core SQLite 提供程序将 EF Core 包作为依赖项进行安装。
  • 基架需要的包:Microsoft.VisualStudio.Web.CodeGeneration.DesignMicrosoft.EntityFrameworkCore.SqlServer

有关允许应用按环境配置其数据库上下文的多个环境配置指南,请参阅 在 ASP.NET Core 中使用多个环境

如果出现基架错误,请验证目标框架名字对象 (TFM) 是否与项目文件中的 NuGet 包版本相匹配。例如,以下项目文件包含适用于 .NET Core 的版本 3.1 和所列的 NuGet 包:

  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2. <PropertyGroup>
  3. <TargetFramework>netcoreapp3.1</TargetFramework>
  4. </PropertyGroup>
  5. <ItemGroup>
  6. <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.0" />
  7. <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
  8. <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.0" />
  9. <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0" />
  10. <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
  11. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
  12. <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
  13. </ItemGroup>
  14. </Project>

从“项目”菜单中选择“管理 NuGet 程序包” 。

在右上方的“搜索”字段中,输入 Microsoft.EntityFrameworkCore.SQLite 并按回车键进行搜索。 搜索匹配的 NuGet 程序包,并按“添加”按钮。

添加 Entity Framework Core NuGet 包

“选择项目”对话框将显示,并已选中 MvcMovie 项目。按“确认”按钮。

“接受许可证”对话框将显示。 根据需要查看许可证,然后单击“接受”按钮。

重复上面的步骤,以安装以下 NuGet 程序包:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Design

创建数据库上下文类Create a database context class

需要一个数据库上下文类来协调 Movie 模型的 EF Core 功能(创建、读取、更新和删除)。数据库上下文派生自 Microsoft.EntityFrameworkCore.DbContext 并指定要包含在数据模型中的实体。

创建一个“Data”文件夹 。

使用以下代码添加 Data/MvcMovieContext.cs 文件 :

  1. using Microsoft.EntityFrameworkCore;
  2. using MvcMovie.Models;
  3. namespace MvcMovie.Data
  4. {
  5. public class MvcMovieContext : DbContext
  6. {
  7. public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
  8. : base(options)
  9. {
  10. }
  11. public DbSet<Movie> Movie { get; set; }
  12. }
  13. }

前面的代码为实体集创建 DbSet<Movie> 属性。在实体框架术语中,实体集通常与数据表相对应。实体对应表中的行。

注册数据库上下文Register the database context

ASP.NET Core 通过依赖关系注入 (DI) 生成。在应用程序启动过程中,必须向 DI 注册服务(如 EF Core DB 上下文)。需要这些服务(如 Razor 页面)的组件通过构造函数提供相应服务。本教程的后续部分介绍了用于获取 DB 上下文实例的构造函数代码。本部分会将数据库上下文注册到 DI 容器。

将以下 using 语句添加到 Startup.cs 顶部 :

  1. using MvcMovie.Data;
  2. using Microsoft.EntityFrameworkCore;

将以下突出显示的代码添加到 Startup.ConfigureServices

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews();
  4. services.AddDbContext<MvcMovieContext>(options =>
  5. options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
  6. }
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews();
  4. services.AddDbContext<MvcMovieContext>(options =>
  5. options.UseSqlite(Configuration.GetConnectionString("MvcMovieContext")));
  6. }

通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文。进行本地开发时, ASP.NET Core 配置系统appsettings.json 文件中读取数据库连接字符串。

添加数据库连接字符串Add a database connection string

将连接字符串添加到 appsettings.json 文件 :

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. }
  8. },
  9. "AllowedHosts": "*",
  10. "ConnectionStrings": {
  11. "MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  12. }
  13. }
  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. }
  8. },
  9. "AllowedHosts": "*",
  10. "ConnectionStrings": {
  11. "MvcMovieContext": "Data Source=MvcMovie.db"
  12. }
  13. }

生成项目以检查编译器错误。

基架电影页面Scaffold movie pages

使用基架工具为电影模型生成“创建”、“读取”、“更新”和“删除”(CRUD) 页面。

在解决方案资源管理器中,右键单击“Controllers”文件夹 >“添加”>“新搭建基架的项目” 。

上述步骤的视图

在“添加基架”对话框中,选择“包含视图的 MVC 控制器(使用 Entity Framework)”>“添加” 。

“添加基架”对话框

填写“添加控制器”对话框:

  • 模型类 :Movie (MvcMovie.Models)
  • 数据上下文类 :MvcMovieContext (MvcMovie.Data)

“添加数据”上下文

  • 视图 :将每个选项保持为默认选中状态
  • 控制器名称: 保留默认的 MoviesController
  • 选择“添加”

Visual Studio 将创建:

  • 电影控制器 (Controllers/MoviesController.cs)
  • “创建”、“删除”、“详细信息”、“编辑”和“索引”页面的 Razor 视图文件 (Views/Movies/*.cshtml)

自动创建这些文件称为“基架” 。

  • 打开项目目录(包含 Program.cs 、Startup.cs 和 .csproj 文件的目录)中的命令窗口。

  • 在 Linux 上,导出基架工具路径:

  1. export PATH=$HOME/.dotnet/tools:$PATH
  • 运行下面的命令:
  1. dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

下表详细说明了 ASP.NET Core 代码生成器参数:

参数描述-m模型的名称。-dc数据上下文。-udl使用默认布局。—relativeFolderPath用于创建文件的相对输出文件夹路径。—useDefaultLayout应为视图使用默认布局。—referenceScriptLibraries向“编辑”和“创建”页面添加 _ValidationScriptsPartial

使用 h 开关获取 aspnet-codegenerator controller 命令方面的帮助:

  1. dotnet aspnet-codegenerator controller -h

有关详细信息,请查看 dotnet aspnet-codegenerator

  • 打开项目目录(包含 Program.cs 、Startup.cs 和 .csproj 文件的目录)中的命令窗口。

  • 运行下面的命令:

  1. dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

下表详细说明了 ASP.NET Core 代码生成器参数:

参数描述-m模型的名称。-dc数据上下文。-udl使用默认布局。—relativeFolderPath用于创建文件的相对输出文件夹路径。—useDefaultLayout应为视图使用默认布局。—referenceScriptLibraries向“编辑”和“创建”页面添加 _ValidationScriptsPartial

使用 h 开关获取 aspnet-codegenerator controller 命令方面的帮助:

  1. dotnet aspnet-codegenerator controller -h

有关详细信息,请查看 dotnet aspnet-codegenerator

你还不能使用基架页面,因为该数据库不存在。如果运行应用并单击“Movie App”链接,则会出现“无法打开数据库”或“无此类表 : Movie”错误消息。

初始迁移Initial migration

使用 EF Core 迁移功能来创建数据库。迁移是可用于创建和更新数据库以匹配数据模型的一组工具。

从“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台”(PMC)。

在 PMC 中,输入以下命令:

  1. Add-Migration InitialCreate
  2. Update-Database
  • Add-Migration InitialCreate:生成 Migrations/{timestamp}_InitialCreate.cs 迁移文件 。InitialCreate 参数是迁移名称。可以使用任何名称,但是按照惯例,会选择可说明迁移的名称。因为这是首次迁移,所以生成的类包含用于创建数据库架构的代码。数据库架构基于在 MvcMovieContext 类中指定的模型。

  • Update-Database:将数据库更新到上一个命令创建的最新迁移。此命令在用于创建数据库的 Migrations/{time-stamp}_InitialCreate.cs 文件中运行 Up 方法 。

数据库更新命令生成以下警告:


No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'.


你可以忽略该警告,它将后面的教程中得到修复。

有关 EF Core 的 PMC 工具的详细信息,请参阅 EF Core 工具引用 - Visual Studio 中的 PMC

运行以下 .NET Core CLI 命令:

  1. dotnet ef migrations add InitialCreate
  2. dotnet ef database update
  • ef migrations add InitialCreate:生成 Migrations/{timestamp}_InitialCreate.cs 迁移文件 。InitialCreate 参数是迁移名称。可以使用任何名称,但是按照惯例,会选择可说明迁移的名称。因为这是首次迁移,所以生成的类包含用于创建数据库架构的代码。数据库架构基于在 MvcMovieContext 类中(位于 Data/MvcMovieContext.cs 文件中)中指定的模型。

  • ef database update:将数据库更新到上一个命令创建的最新迁移。此命令在用于创建数据库的 Migrations/{time-stamp}_InitialCreate.cs 文件中运行 Up 方法 。

有关 EF Core 的 CLI 的详细信息,请参阅 .Net CLI 的 EF Core 工具引用

InitialCreate 类The InitialCreate class

检查 Migrations/{timestamp}_InitialCreate.cs 迁移文件 :

  1. public partial class Initial : Migration
  2. {
  3. protected override void Up(MigrationBuilder migrationBuilder)
  4. {
  5. migrationBuilder.CreateTable(
  6. name: "Movie",
  7. columns: table => new
  8. {
  9. Id = table.Column<int>(nullable: false)
  10. .Annotation("SqlServer:ValueGenerationStrategy",
  11. SqlServerValueGenerationStrategy.IdentityColumn),
  12. Title = table.Column<string>(nullable: true),
  13. ReleaseDate = table.Column<DateTime>(nullable: false),
  14. Genre = table.Column<string>(nullable: true),
  15. Price = table.Column<decimal>(nullable: false)
  16. },
  17. constraints: table =>
  18. {
  19. table.PrimaryKey("PK_Movie", x => x.Id);
  20. });
  21. }
  22. protected override void Down(MigrationBuilder migrationBuilder)
  23. {
  24. migrationBuilder.DropTable(
  25. name: "Movie");
  26. }
  27. }

Up 方法创建 Movie 表,并将 Id 配置为主键。Down 方法可还原 Up 迁移所做的架构更改。

测试应用Test the app

  • 运行应用并单击“Movie App”链接 。

如果遇到类似于以下情况的异常:

  1. SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.
  1. SqliteException: SQLite Error 1: 'no such table: Movie'.

可能缺失迁移步骤

  • 测试“创建”页 。输入并提交数据。

备注

可能无法在 Price 字段中输入十进制逗号。若要使 jQuery 验证支持使用逗号(“,”)表示小数点的非英语区域设置,以及支持非美国英语日期格式,应用必须进行全球化。有关全球化的说明,请参阅此 GitHub 问题

  • 测试“编辑”、“详细信息”和“删除”页 。

控制器中的依赖项注入Dependency injection in the controller

打开 Controllers/MoviesController.cs 文件并检查构造函数 :

  1. public class MoviesController : Controller
  2. {
  3. private readonly MvcMovieContext _context;
  4. public MoviesController(MvcMovieContext context)
  5. {
  6. _context = context;
  7. }

构造函数使用依赖关系注入将数据库上下文 (MvcMovieContext) 注入到控制器中。数据库上下文将在控制器中的每个 CRUD 方法中使用。

  1. public class MoviesController : Controller
  2. {
  3. private readonly MvcMovieContext _context;
  4. public MoviesController(MvcMovieContext context)
  5. {
  6. _context = context;
  7. }

构造函数使用依赖关系注入将数据库上下文 (MvcMovieContext) 注入到控制器中。数据库上下文将在控制器中的每个 CRUD 方法中使用。

将 SQLite 用于开发,将 SQL Server 用于生产Use SQLite for development, SQL Server for production

选择 SQLite 后,模板生成的代码便可用于开发。下面的代码演示如何将 IWebHostEnvironment 注入到 Startup 中。注入 IWebHostEnvironment,以便 ConfigureServices 可以在开发中使用 SQLite 并在生产中使用 SQL Server。

  1. public class Startup
  2. {
  3. public Startup(IConfiguration configuration, IWebHostEnvironment env)
  4. {
  5. Environment = env;
  6. Configuration = configuration;
  7. }
  8. public IConfiguration Configuration { get; }
  9. public IWebHostEnvironment Environment { get; }
  10. public void ConfigureServices(IServiceCollection services)
  11. {
  12. services.AddControllersWithViews();
  13. services.AddDbContext<MvcMovieContext>(options =>
  14. {
  15. var connectionString = Configuration.GetConnectionString("MvcMovieContext");
  16. if (Environment.IsDevelopment())
  17. {
  18. options.UseSqlite(connectionString);
  19. }
  20. else
  21. {
  22. options.UseSqlServer(connectionString);
  23. }
  24. });
  25. }
  26. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  27. {
  28. if (env.IsDevelopment())
  29. {
  30. app.UseDeveloperExceptionPage();
  31. }
  32. else
  33. {
  34. app.UseExceptionHandler("/Home/Error");
  35. app.UseHsts();
  36. }
  37. app.UseHttpsRedirection();
  38. app.UseStaticFiles();
  39. app.UseRouting();
  40. app.UseAuthorization();
  41. app.UseEndpoints(endpoints =>
  42. {
  43. endpoints.MapControllerRoute(
  44. name: "default",
  45. pattern: "{controller=Home}/{action=Index}/{id?}");
  46. });
  47. }
  48. }

强类型模型和 @model 关键词Strongly typed models and the @model keyword

在本教程之前的内容中,已经介绍了控制器如何使用 ViewData 字典将数据或对象传递给视图。ViewData 字典是一个动态对象,提供了将信息传递给视图的方便的后期绑定方法。

MVC 还提供将强类型模型对象传递给视图的功能。此强类型方法启用编译时代码检查。基架机制通过 MoviesController 类和视图使用了此方法(即传递强类型模型)。

检查 Controllers/MoviesController.cs 文件中生成的 Details 方法 :

  1. // GET: Movies/Details/5
  2. public async Task<IActionResult> Details(int? id)
  3. {
  4. if (id == null)
  5. {
  6. return NotFound();
  7. }
  8. var movie = await _context.Movie
  9. .FirstOrDefaultAsync(m => m.Id == id);
  10. if (movie == null)
  11. {
  12. return NotFound();
  13. }
  14. return View(movie);
  15. }

id 参数通常作为路由数据传递。例如 https://localhost:5001/movies/details/1 的设置如下:

  • 控制器被设置为 movies 控制器(第一个 URL 段)。
  • 操作被设置为 details(第二个 URL 段)。
  • ID 被设置为 1(最后一个 URL 段)。

还可以使用查询字符串传入 id,如下所示:

https://localhost:5001/movies/details?id=1

在未提供 ID 值的情况下,id 参数可定义为可以为 null 的类型 (int?)。

Lambda 表达式会被传入 FirstOrDefaultAsync 以选择与路由数据或查询字符串值相匹配的电影实体。

  1. var movie = await _context.Movie
  2. .FirstOrDefaultAsync(m => m.Id == id);

如果找到了电影,Movie 模型的实例则会被传递到 Details 视图:

  1. return View(movie);

检查 Views/Movies/Details.cshtml 文件的内容 :

  1. @model MvcMovie.Models.Movie
  2. @{
  3. ViewData["Title"] = "Details";
  4. }
  5. <h1>Details</h1>
  6. <div>
  7. <h4>Movie</h4>
  8. <hr />
  9. <dl class="row">
  10. <dt class="col-sm-2">
  11. @Html.DisplayNameFor(model => model.Title)
  12. </dt>
  13. <dd class="col-sm-10">
  14. @Html.DisplayFor(model => model.Title)
  15. </dd>
  16. <dt class="col-sm-2">
  17. @Html.DisplayNameFor(model => model.ReleaseDate)
  18. </dt>
  19. <dd class="col-sm-10">
  20. @Html.DisplayFor(model => model.ReleaseDate)
  21. </dd>
  22. <dt class="col-sm-2">
  23. @Html.DisplayNameFor(model => model.Genre)
  24. </dt>
  25. <dd class="col-sm-10">
  26. @Html.DisplayFor(model => model.Genre)
  27. </dd>
  28. <dt class="col-sm-2">
  29. @Html.DisplayNameFor(model => model.Price)
  30. </dt>
  31. <dd class="col-sm-10">
  32. @Html.DisplayFor(model => model.Price)
  33. </dd>
  34. </dl>
  35. </div>
  36. <div>
  37. <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
  38. <a asp-action="Index">Back to List</a>
  39. </div>

视图文件顶部的 @model 语句可指定视图期望的对象类型。创建影片控制器时,将包含以下 @model 语句:

  1. @model MvcMovie.Models.Movie

@model 指令允许访问控制器传递给视图的影片。Model 对象为强类型对象。例如,在 Details.cshtml 视图中,代码通过强类型的 Model 对象将每个电影字段传递给 DisplayNameForDisplayForHTML 帮助程序 。CreateEdit 方法以及视图也传递一个 Movie 模型对象。

检查电影控制器中的 Index.cshtml 视图和 Index 方法 。请注意代码在调用 View 方法时是如何创建 List 对象的。代码将此 Movies 列表从 Index 操作方法传递给视图:

  1. // GET: Movies
  2. public async Task<IActionResult> Index()
  3. {
  4. return View(await _context.Movie.ToListAsync());
  5. }

创建影片控制器后,基架将以下 @model 语句包含在 Index.cshtml 文件的顶部 :

  1. @model IEnumerable<MvcMovie.Models.Movie>

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的电影列表。例如,在 Index.cshtml 视图中,代码使用 foreach 语句通过强类型 Model 对象对电影进行循环遍历 :

  1. @model IEnumerable<MvcMovie.Models.Movie>
  2. @{
  3. ViewData["Title"] = "Index";
  4. }
  5. <h1>Index</h1>
  6. <p>
  7. <a asp-action="Create">Create New</a>
  8. </p>
  9. <table class="table">
  10. <thead>
  11. <tr>
  12. <th>
  13. @Html.DisplayNameFor(model => model.Title)
  14. </th>
  15. <th>
  16. @Html.DisplayNameFor(model => model.ReleaseDate)
  17. </th>
  18. <th>
  19. @Html.DisplayNameFor(model => model.Genre)
  20. </th>
  21. <th>
  22. @Html.DisplayNameFor(model => model.Price)
  23. </th>
  24. <th></th>
  25. </tr>
  26. </thead>
  27. <tbody>
  28. @foreach (var item in Model) {
  29. <tr>
  30. <td>
  31. @Html.DisplayFor(modelItem => item.Title)
  32. </td>
  33. <td>
  34. @Html.DisplayFor(modelItem => item.ReleaseDate)
  35. </td>
  36. <td>
  37. @Html.DisplayFor(modelItem => item.Genre)
  38. </td>
  39. <td>
  40. @Html.DisplayFor(modelItem => item.Price)
  41. </td>
  42. <td>
  43. <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
  44. <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
  45. <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
  46. </td>
  47. </tr>
  48. }
  49. </tbody>
  50. </table>

因为 Model 对象为强类型(作为 IEnumerable<Movie> 对象),因此循环中的每个项都被类型化为 Movie除其他优点之外,这意味着可对代码进行编译时检查。

其他资源Additional resources

上一篇:添加视图下一篇:使用 SQL

添加数据模型类Add a data model class

右键单击 Models 文件夹,然后单击“添加” > “类” 。将类命名“Movie” 。

Movie 类添加以下属性:

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. namespace MvcMovie.Models
  4. {
  5. public class Movie
  6. {
  7. public int Id { get; set; }
  8. public string Title { get; set; }
  9. [DataType(DataType.Date)]
  10. public DateTime ReleaseDate { get; set; }
  11. public string Genre { get; set; }
  12. public decimal Price { get; set; }
  13. }
  14. }

Movie 类包含:

  • 数据库需要 Id 字段以获取主键。

  • [DataType(DataType.Date)]DataType 属性指定数据的类型 (Date)。通过此特性:

    • 用户无需在数据字段中输入时间信息。
    • 仅显示日期,而非时间信息。

DataAnnotations 会在后续教程中介绍。

  • 将类添加到名为“Movie.cs” 的“Models” 文件夹。

Movie 类添加以下属性:

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. namespace MvcMovie.Models
  4. {
  5. public class Movie
  6. {
  7. public int Id { get; set; }
  8. public string Title { get; set; }
  9. [DataType(DataType.Date)]
  10. public DateTime ReleaseDate { get; set; }
  11. public string Genre { get; set; }
  12. public decimal Price { get; set; }
  13. }
  14. }

Movie 类包含:

  • 数据库需要 Id 字段以获取主键。

  • [DataType(DataType.Date)]DataType 属性指定数据的类型 (Date)。通过此特性:

    • 用户无需在数据字段中输入时间信息。
    • 仅显示日期,而非时间信息。

DataAnnotations 会在后续教程中介绍。

创建一个“Data”文件夹 。

将以下 MvcMovieContext 类添加到“Data”文件夹 :

  1. using Microsoft.EntityFrameworkCore;
  2. using MvcMovie.Models;
  3. namespace MvcMovie.Data
  4. {
  5. public class MvcMovieContext : DbContext
  6. {
  7. public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
  8. : base(options)
  9. {
  10. }
  11. public DbSet<Movie> Movie { get; set; }
  12. }
  13. }

前面的代码为实体集创建 DbSet 属性。在 Entity Framework 中,实体集通常与数据表相对应,具体实体与表中的行相对应。

添加数据库连接字符串Add a database connection string

将连接字符串添加到 appsettings.json 文件 :

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. }
  8. },
  9. "AllowedHosts": "*",
  10. "ConnectionStrings": {
  11. "MvcMovieContext": "Data Source=MvcMovie.db"
  12. }
  13. }

添加 NuGet 包和 EF 工具Add NuGet packages and EF tools

运行以下 .NET Core CLI 命令:

  1. dotnet tool install --global dotnet-ef
  2. dotnet tool install --global dotnet-aspnet-codegenerator
  3. dotnet add package Microsoft.EntityFrameworkCore.SQLite
  4. dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
  5. dotnet add package Microsoft.EntityFrameworkCore.Design
  6. dotnet add package Microsoft.EntityFrameworkCore.SqlServer

上述命令添加:

  • aspnet-codegenerator 基架工具
  • 适用于 .NET Core CLI 的 Entity Framework Core 工具。
  • EF Core SQLite 提供程序将 EF Core 包作为依赖项进行安装。
  • 基架需要的包:Microsoft.VisualStudio.Web.CodeGeneration.DesignMicrosoft.EntityFrameworkCore.SqlServer

有关允许应用按环境配置其数据库上下文的多个环境配置指南,请参阅 在 ASP.NET Core 中使用多个环境

如果出现基架错误,请验证目标框架名字对象 (TFM) 是否与项目文件中的 NuGet 包版本相匹配。例如,以下项目文件包含适用于 .NET Core 的版本 3.1 和所列的 NuGet 包:

  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2. <PropertyGroup>
  3. <TargetFramework>netcoreapp3.1</TargetFramework>
  4. </PropertyGroup>
  5. <ItemGroup>
  6. <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.0" />
  7. <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
  8. <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.0" />
  9. <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0" />
  10. <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
  11. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
  12. <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
  13. </ItemGroup>
  14. </Project>

注册数据库上下文Register the database context

将以下 using 语句添加到 Startup.cs 顶部 :

using MvcMovie.Data;
using Microsoft.EntityFrameworkCore;

使用 Startup.ConfigureServices 中的依赖关系注入容器注册数据库上下文。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<MvcMovieContext>(options =>
            options.UseSqlite(Configuration.GetConnectionString("MvcMovieContext")));
}

生成项目以检查编译器错误。

将以下 MvcMovieContext 类添加到“模型”文件夹 :

using Microsoft.EntityFrameworkCore;

namespace MvcMovie.Models
{
    public class MvcMovieContext : DbContext
    {
        public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
            : base(options)
        {
        }

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

前面的代码为实体集创建 DbSet 属性。在 Entity Framework 中,实体集通常与数据表相对应,具体实体与表中的行相对应。

添加数据库连接字符串Add a database connection string

将连接字符串添加到 appsettings.json 文件 :

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "MovieContext": "Data Source=MvcMovie.db"
  }
}

添加所需的 NuGet 包Add required NuGet packages

运行以下 .NET Core CLI 命令,以将 SQLite 和 CodeGeneration.Design 添加到项目中:

dotnet add package Microsoft.EntityFrameworkCore.SQLite
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

Microsoft.VisualStudio.Web.CodeGeneration.Design 包对基架是必需的。

注册数据库上下文Register the database context

将以下 using 语句添加到 Startup.cs 顶部 :

using MvcMovie.Models;
using Microsoft.EntityFrameworkCore;

使用 Startup.ConfigureServices 中的依赖关系注入容器注册数据库上下文。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for 
        // non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<MvcMovieContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("MovieContext")));

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

生成项目以检查错误。

搭建“电影”模型的基架Scaffold the movie model

在此部分,将搭建“电影”模型的基架。确切地说,基架工具将生成页面,用于对“电影”模型执行创建、读取、更新和删除 (CRUD) 操作。

在解决方案资源管理器中,右键单击“Controllers”文件夹 >“添加”>“新搭建基架的项目” 。

上述步骤的视图

在“添加基架”对话框中,选择“包含视图的 MVC 控制器(使用 Entity Framework)”>“添加” 。

“添加基架”对话框

填写“添加控制器”对话框:

  • 模型类 :Movie (MvcMovie.Models)
  • 数据上下文类 :选择 + 图标并添加默认的 MvcMovie.Models.MvcMovieContext

“添加数据”上下文

  • 视图 :将每个选项保持为默认选中状态
  • 控制器名称: 保留默认的 MoviesController
  • 选择“添加”

“添加控制器”对话框

Visual Studio 将创建:

  • Entity Framework Core 数据库上下文类 (Data/MvcMovieContext.cs)
  • 电影控制器 (Controllers/MoviesController.cs)
  • “创建”、“删除”、“详细信息”、“编辑”和“索引”页面的 Razor 视图文件 (Views/Movies/*.cshtml)

自动创建数据库上下文和 CRUD(创建、读取、更新和删除)操作方法和视图的过程称为“搭建基架” 。

  • 打开项目目录(包含 Program.cs 、Startup.cs 和 .csproj 文件的目录)中的命令窗口。

  • 安装基架工具:

dotnet tool install --global dotnet-aspnet-codegenerator
  • 在 Linux 上,导出基架工具路径:
  export PATH=$HOME/.dotnet/tools:$PATH
  • 运行下面的命令:
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

下表详细说明了 ASP.NET Core 代码生成器参数:

参数描述
-m模型的名称。
-dc数据上下文。
-udl使用默认布局。
—relativeFolderPath用于创建文件的相对输出文件夹路径。
—useDefaultLayout应为视图使用默认布局。
—referenceScriptLibraries向“编辑”和“创建”页面添加 _ValidationScriptsPartial

使用 h 开关获取 aspnet-codegenerator controller 命令方面的帮助:

dotnet aspnet-codegenerator controller -h

有关详细信息,请查看 dotnet aspnet-codegenerator

  • 打开项目目录(包含 Program.cs 、Startup.cs 和 .csproj 文件的目录)中的命令窗口。

  • 安装基架工具:

dotnet tool install --global dotnet-aspnet-codegenerator
  • 运行下面的命令:
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries

下表详细说明了 ASP.NET Core 代码生成器参数:

参数描述
-m模型的名称。
-dc数据上下文。
-udl使用默认布局。
—relativeFolderPath用于创建文件的相对输出文件夹路径。
—useDefaultLayout应为视图使用默认布局。
—referenceScriptLibraries向“编辑”和“创建”页面添加 _ValidationScriptsPartial

使用 h 开关获取 aspnet-codegenerator controller 命令方面的帮助:

dotnet aspnet-codegenerator controller -h

有关详细信息,请查看 dotnet aspnet-codegenerator

如果运行应用并单击“Mvc 电影”链接,则会出现以下类似的错误 :

An unhandled exception occurred while processing the request.

SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.

System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString
An unhandled exception occurred while processing the request.
SqliteException: SQLite Error 1: 'no such table: Movie'.
Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(int rc, sqlite3 db)

你需要创建数据库,并且使用 EF Core 迁移功能来执行此操作。通过迁移可创建与数据模型匹配的数据库,并在数据模型更改时更新数据库架构。

初始迁移Initial migration

在本节中,将完成以下任务:

  • 添加初始迁移。
  • 使用初始迁移来更新数据库。
  • 从“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台”(PMC)。

PMC 菜单

  • 在 PMC 中,输入以下命令:
Add-Migration Initial
Update-Database

Add-Migration 命令生成用于创建初始数据库架构的代码。

数据库架构基于在 MvcMovieContext 类中指定的模型。Initial 参数是迁移名称。可以使用任何名称,但是按照惯例,会使用可说明迁移的名称。有关详细信息,请参阅 教程:使用迁移功能 - ASP.NET MVC 和 EF Core

Update-Database 命令在用于创建数据库的 Migrations/{time-stamp}_InitialCreate.cs 文件中运行 Up 方法 。

运行以下 .NET Core CLI 命令:

dotnet ef migrations add InitialCreate
dotnet ef database update

ef migrations add InitialCreate 命令生成用于创建初始数据库架构的代码。

数据库架构基于在 MvcMovieContext 类中(位于 Data/MvcMovieContext.cs 文件中)中指定的模型。InitialCreate 参数是迁移名称。可以使用任何名称,但是按照惯例,会选择可说明迁移的名称。

检查通过依赖关系注入注册的上下文Examine the context registered with dependency injection

ASP.NET Core 通过依赖关系注入 (DI) 生成。服务(例如 EF Core 数据库上下文)在应用程序启动期间通过 DI 注册。需要这些服务(如 Razor 页面)的组件通过构造函数提供相应服务。本教程的后续部分介绍了用于获取 DB 上下文实例的构造函数代码。

基架工具自动创建数据库上下文并将其注册到 DI 容器。

我们来研究以下 Startup.ConfigureServices 方法。基架添加了突出显示的行:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies 
        // is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddDbContext<MvcMovieContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

MvcMovieContextMovie 模型协调 EF Core 功能(创建、读取、更新、删除等)。数据上下文 (MvcMovieContext) 派生自 Microsoft.EntityFrameworkCore.DbContext数据上下文指定数据模型中包含哪些实体:

// Unused usings removed.
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;  // Enables public DbSet<Movie> Movie

namespace MvcMovie.Data
{
    public class MvcMovieContext : DbContext
    {
        public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
            : base(options)
        {
        }

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

前面的代码为实体集创建 DbSet<Movie> 属性。在实体框架术语中,实体集通常与数据表相对应。实体对应表中的行。

通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文。进行本地开发时, ASP.NET Core 配置系统appsettings.json 文件中读取数据库连接字符串。

创建了数据库上下文并将其注册到了 DI 容器。

测试应用Test the app

如果收到如下所示数据库异常:

SqlException: Cannot open database "MvcMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.

缺少迁移步骤

  • 测试“创建” 链接。输入并提交数据。

备注

可能无法在 Price 字段中输入十进制逗号。若要使 jQuery 验证支持使用逗号(“,”)表示小数点的非英语区域设置,以及支持非美国英语日期格式,应用必须进行全球化。有关全球化的说明,请参阅此 GitHub 问题

  • 测试“编辑” 、“详细信息” 和“删除” 链接。

检查 Startup 类:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies 
        // is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddDbContext<MvcMovieContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}

上面突出显示的代码显示了要添加到依赖关系注入容器的电影数据库上下文:

  • services.AddDbContext<MvcMovieContext>(options => 指定要使用的数据库和连接字符串。
  • =>lambda 运算符

打开 Controllers/MoviesController.cs 文件并检查构造函数 :

public class MoviesController : Controller
{
    private readonly MvcMovieContext _context;

    public MoviesController(MvcMovieContext context)
    {
        _context = context;
    }

构造函数使用依赖关系注入将数据库上下文 (MvcMovieContext) 注入到控制器中。数据库上下文将在控制器中的每个 CRUD 方法中使用。

强类型模型和 @model 关键词Strongly typed models and the @model keyword

在本教程之前的内容中,已经介绍了控制器如何使用 ViewData 字典将数据或对象传递给视图。ViewData 字典是一个动态对象,提供了将信息传递给视图的方便的后期绑定方法。

MVC 还提供将强类型模型对象传递给视图的功能。凭借此强类型方法可更好地对代码进行编译时检查。基架机制在创建方法和视图时,通过 MoviesController 类和视图使用了此方法(即传递强类型模型)。

检查 Controllers/MoviesController.cs 文件中生成的 Details 方法 :

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie
        .FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

id 参数通常作为路由数据传递。例如 https://localhost:5001/movies/details/1 的设置如下:

  • 控制器被设置为 movies 控制器(第一个 URL 段)。
  • 操作被设置为 details(第二个 URL 段)。
  • ID 被设置为 1(最后一个 URL 段)。

还可以使用查询字符串传入 id,如下所示:

https://localhost:5001/movies/details?id=1

在未提供 ID 值的情况下,id 参数可定义为可以为 null 的类型 (int?)。

Lambda 表达式会被传入 FirstOrDefaultAsync 以选择与路由数据或查询字符串值相匹配的电影实体。

var movie = await _context.Movie
    .FirstOrDefaultAsync(m => m.Id == id);

如果找到了电影,Movie 模型的实例则会被传递到 Details 视图:

return View(movie);

检查 Views/Movies/Details.cshtml 文件的内容 :

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Movie</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

通过将 @model 语句包括在视图文件的顶端,可以指定视图期望的对象类型。创建电影控制器时,会自动在 Details.cshtml 文件的顶端包括以下 @model 语句 :

@model MvcMovie.Models.Movie

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的电影。例如,在 Details.cshtml 视图中,代码通过强类型的 Model 对象将每个电影字段传递给 DisplayNameForDisplayForHTML 帮助程序 。CreateEdit 方法以及视图也传递一个 Movie 模型对象。

检查电影控制器中的 Index.cshtml 视图和 Index 方法 。请注意代码在调用 View 方法时是如何创建 List 对象的。代码将此 Movies 列表从 Index 操作方法传递给视图:

// GET: Movies
public async Task<IActionResult> Index()
{
    return View(await _context.Movie.ToListAsync());
}

创建电影控制器时,基架会自动在 Index.cshtml 文件的顶端包含以下 @model 语句 :

@model IEnumerable<MvcMovie.Models.Movie>

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的电影列表。例如,在 Index.cshtml 视图中,代码使用 foreach 语句通过强类型 Model 对象对电影进行循环遍历 :

@model IEnumerable<MvcMovie.Models.Movie>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

因为 Model 对象为强类型(作为 IEnumerable<Movie> 对象),因此循环中的每个项都被类型化为 Movie除其他优点之外,这意味着可对代码进行编译时检查:

其他资源Additional resources

上一篇:添加视图下一篇:使用数据库