添加模型
作者: Rick Anderson 以及 Tom Dykstra
翻译: 娄宇(Lyrics)
校对: 许登洋(Seay) 、姚阿勇(Mr.Yao) 、夏申斌 、孟帅洋(书缘)
在这一节里,你将添加一些类来管理数据库中的电影数据。这些类将成为 MVC 应用程序中的 "Model" 部分。
你将使用 .NET Framework 中名为 Entity Framework Core (EF Core) 的数据库访问技术来使用这些数据模型类。EF Core 是一种可以用来简化数据访问代码的关系对象映射 (ORM) 框架。本教程中你会使用到 SQLite 数据库,但是EF Core 还支持更多的数据库。
你创建的模型类也被称为 POCO 类 (源自 "plain-old CLR objects." ),这些类无需和 EF Core 有任何依赖。他们的作用仅仅是定义数据库属性并且能够存储到数据库。
本文中你将先编写一些数据模型类,然后 EF Core 根据你的类创建数据库。另外一种没有介绍的方式是从已经存在的数据库生成模型类,关于这个方式的信息,参考 ASP.NET Core - 已存在数据库。
添加数据模型类
- 添加一个文件夹命名为 Models。
- 在 Models 文件夹中添加一个类命名为 Movie.cs。
- 添加下列代码到 Models/Movie.cs 文件:
using System; namespace MvcMovie.Models { public class Movie { public int ID { get; set; } public string Title { get; set; } public DateTime ReleaseDate { get; set; } public string Genre { get; set; } public decimal Price { get; set; } } }
ID
字段需要用来作为数据库主键
编译项目并且检查是否有错误,最后我们成功的把 Model 添加到了你的 MVC 应用程序。
准备项目基架
添加下列高亮 NuGet 包到 MvcMovie.csproj 文件:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> </ItemGroup> </Project>
保存文件并在弹出 Info 信息 "There are unresolved dependencies" 的时候点击 Restore。
创建 Models/MvcMovieContext.cs 文并添加下面的
MvcMovieContext
类:using Microsoft.EntityFrameworkCore; namespace MvcMovie.Models { public class MvcMovieContext : DbContext { public MvcMovieContext (DbContextOptions<MvcMovieContext> options) : base(options) { } public DbSet<MvcMovie.Models.Movie> Movie { get; set; } } }
打开 Startup.cs 文件并添加以下2个 usings 语句:
using Microsoft.EntityFrameworkCore; using MvcMovie.Models; namespace MvcMovie { public class Startup {
添加数据上下文到 Startup.cs 文件:
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddDbContext<MvcMovieContext>(options => options.UseSqlite("Data Source=MvcMovie.db")); }
This tells Entity Framework which model classes are included in the data model. You're defining one entity set of Movie objects, which will be represented in the database as a Movie table.
编译项目确认没有任何错误。
基架生成 MovieController
在项目文件打开终端窗口,运行一下命令:
dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
备注
如果你在运行基架命令的时候遇到错误信息, 请参考 issue 444 in the scaffolding repository 作为解决方案。
基架引擎产生以下输出:
- 一个电影控制器(Controller)(Controllers/MoviesController.cs)
- Create、Delete、Details、Edit 以及 Index 的 Razor 视图文件(Views/Movies)
自动创建 数据库上下文以及 CRUD(创建、读取、更新以及删除)Action 方法和视图(View)(自动创建 CRUD Action 方法和 View 视图被称为 搭建基架(scaffolding))。很快你将拥有一个可以让你创建、查看、编辑以及删除电影条目的完整功能的 Web 应用程序。
创建数据库
你可以调用 EnsureCreated
方法来通知 EF Core 在数据库不存在的情况下去创建数据库。
这个方法你只能在开发环境中显式的调用。 当应用程序第一次运行的时候它会创建符合你的数据模型的数据库, 当年你修改了数据模型, 然后删除了数据库, 下一次应用程序运行, EF Core 又会依据你的数据模型创建一个新的数据库。
这种方式在生产环境中使用中并不合适, 因为数据库中有了数据,而你又不想删除数据库导致数据丢失。 EF Core 包含一个 Migrations 功能来让你在数据模型变更的时候防止数据丢失,但是在本节教程中,你不会用到 Migrations 功能。 在添加新字段教程中,你将了解到更多关于数据模型变更的知识。
Create a Models\DBinitialize.cs file and add the following code:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace MvcMovie.Models
{
public static class DBinitialize
{
public static void EnsureCreated(IServiceProvider serviceProvider)
{
var context = new MvcMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>());
context.Database.EnsureCreated();
}
}
}
在 Startup.cs 文件的 Configure
方法中调用 EnsureCreated
方法。在方法的最后添加调用:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
DBinitialize.EnsureCreated(app.ApplicationServices);
}
测试一下
- 运行应用程序并点击 Mvc Movie 链接。
点击 Create New 链接并创建电影记录。
你也许不能在
Price
字段中输入小数点或逗号。为了实现对非英语环境中用逗号(",")来表示小数点,以及非美国英语日期格式的 jQuery 验证 ,你必须采取措施国际化你的应用程序。查看 额外的资源 获取更多的信息。现在仅仅输入完整的数字,比如10。
- 在某些地区你需要指定日期格式。查看下方高亮代码。
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
我们在后续的教程会谈到 DataAnnotations
。
点击 Create 提交表单到服务器,将电影数据保存到数据库中。然后重定向到 /Movies URL ,你可以在列表中看到新创建的电影。
再创建几个电影条目。尝试 Edit 、 Details 、 Delete 链接来执行各个功能。
依赖注入
打开 Startup.cs 类文件查看 ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddDbContext<MvcMovieContext>(options =>
上面高亮代码显示 movie 数据库上下文已经倍添加到 依赖注入 容器。 代码 services.AddDbContext<MvcMovieContext>(options =>
后面的没有被现实(查看代码),使用连接字符串来指定数据库。=>
is a lambda 操作.
打开 Controllers/MoviesController.cs 文件查看构造器:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
构造器使用 依赖注入 来注入数据库上下文 (MvcMovieContext
) 到控制器, 数据库上下文在控制器的所有的 CRUD 方法中使用。
强类型模型与 @model 关键字
在之前的教程中,你看到了控制器(Controller)如何通过 ViewData
字典传递数据到一个视图(View)。 ViewData
字典是一个动态类型对象,它提供了一种便捷的后期绑定方式将信息传递给视图。
MVC 也提供了传递强类型数据给视图的能力。这种强类型的方式可以提供给你更好的代码编译时检查,并在 Visual Studio(VS) 中具有更丰富的 智能感知 。VS 中的基架机制在为 MoviesController
类创建方法(Action)和视图(View)的时候就采用了这种方式(即,传递强类型模型)。
检查在 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
.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
id
参数一般作为路由数据传递,例如 http://localhost:5000/movies/details/1
将:
- 设置为
movies
(对应第一个 URL 段) - Action 设置为
details
(对应第二个 URL 段) - id 设置为 1(对应最后一个 URL 段)
你也可以向下面一样通过查询字符串(Query String)传递 id
:
http://localhost:1234/movies/details?id=1
id
参数被定义为 可空类型 (int?
) 来对应没有ID值的情况。
lambda 表达式 传给 SingleOrDefaultAsync
方法来选择匹配路由数据以及查询字符串的电影实体类。
var movie = await _context.Movie
.SingleOrDefaultAsync(m => m.ID == id);
如果电影被找到了, Movie
模型(Model)的实例将被传递给 Details
视图(View)。
return View(movie);
查看 Views/Movies/Details.cshtml 文件的内容:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@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>
通过在视图(View)文件顶部加入一个 @model
语句,你可以指定视图(View)所期望的对象类型。当你创建这个 MoviesController 时, Visual Studio 自动在 Details.cshtml 顶部加入了 @model
语句后面的部分。
@model MvcMovie.Models.Movie
@model
指令允许你访问从控制器(Controller)传递给视图(View)的这个强类型电影 Model
对象。例如,在 Details.cshtml 视图中,代码用强类型 Model
对象传递所有的电影字段到 DisplayNameFor
和 DisplayFor
HTML 帮助类(HTML Helper)里。 Create
和 Edit
方法和视图(View)也传递一个 Movie
模型(Model)对象。
检查 Index.cshtml 视图(View)和 MoviesController 里的 Index
方法。注意观察代码在调用 View
方法时,是如何创建一个 列表(List)
对象的。这段代码将s Movies
列表从 Index
Action 方法传递给视图(View):
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
当你创建这个 MoviesController 时,Visual Studio 自动在 Index.cshtml 顶部加入以下 @model
语句:
@model IEnumerable<MvcMovie.Models.Movie>
@model
指令允许你访问电影列表这个从控制器(Controller)传递给视图(View)的强类型 Model
对象。例如,在 Index.cshtml 视图中,代码通过 foreach
语句遍历了电影列表这个强类型的 模型(Model)
对象。
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<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>
对象),循环中的每一个 item 的类型被类型化为 Movie
。除了其他好处外,这意味着你将获得代码的编译时检查以及在代码编辑器里得到完整的 智能感知 支持:
这样的话,你就拥有了一个数据库,以及对应的显示,编辑,修改 和删除数据的页面。 在下一节的教程中, 来我们要处理数据库的问题了。