ASP.NET Core 和 EF Core 系列教程——入门

作者:Tom DykstraRick Anderson

此处提供了本教程的 Razor 页版本。 Razor 页版本更容易体现和覆盖 EF 的功能。 我们建议你学习本教程的 Razor 页版本。

Contoso 大学示例 web 应用程序演示如何使用 Entity Framework (EF) Core 2.0 和 Visual Studio 2017 创建 ASP.NET Core 2.0 MVC web 应用程序。

示例应用程序供一个虚构的 Contoso 大学网站使用。 它包括诸如学生入学、 课程创建和导师分配等功能。 这是一系列教程中的第一个,这一系列教程主要展示了如何从零开始构建 Contoso 大学示例应用程序。

下载或查看已完成的应用程序。

EF Core 2.0 是 EF 的最新版本,但还没有包括 EF 6.x 的所有功能 。 有关如何在 EF 6.x 和 EF Core 之间选择,请参阅EF Core vs. EF6.x。 如果你选择使用 EF 6.x,请参阅本系列教程的上一个版本

准备

安装以下组件:

  • .NET Core 2.0.0 SDK 或更高版本。
  • 已安装 ASP.NET 和 Web 开发工作负载的 Visual Studio 2017 15.3 版或更高版本。

疑难解答

如果你遇到无法解决的问题,可以通过比较已完成的项目查找解决方案。常见错误以及对应的解决方案,请参阅最新教程中的故障排除。 如果没有找到遇到的问题的解决方案你可以将问题发布到StackOverflow.com 的 ASP.NET CoreEF Core版块。

这是一系列一共有十个教程,其中每个都是在前面教程已完成的基础上继续。请考虑在完成每一个教程后保存项目的副本。之后如果遇到问题,你可以从保存的副本中开始寻找问题,而不是从头开始。

Contoso 大学 web 应用程序

你将在这些教程中学习构建一个简单的大学网站的应用程序。

用户可以查看和更新学生、 课程和教师信息。 以下是一些你即将创建的页面。

学生索引页
学生编辑页

本教程主要关注于如何使用 Entity Framework , 所以此站点的UI样式都是直接套用内置的模板。

创建 ASP.NET Core MVC web 应用程序

打开 Visual Studio 并创建一个新 ASP.NET Core C# web 项目名为”ContosoUniversity”。

  • 文件菜单上,选择新建 > 项目

  • 从左窗格中,选择已安装 > Visual C# > Web

  • 选择“ASP.NET Core Web 应用程序”项目模板。

  • 输入ContosoUniversity作为名称,然后单击确定

    “新建项目”对话框
  • 等待新 ASP.NET Core Web 应用程序 (.NET Core)显示对话框

  • 选择ASP.NET Core 2.0Web 应用程序 (模型-视图-控制器)模板。

    注意:本教程需要安装 ASP.NET Core 2.0 和 EF Core 2.0 或更高版本-请确保ASP.NET Core 1.1未选中。

  • 请确保身份验证设置为不进行身份验证

  • 单击“确定”

    “新建 ASP.NET 项目”对话框

设置站点样式

通过几个简单的更改设置站点菜单、 布局和主页。

打开Views/Shared/_Layout.cshtml并进行以下更改:

  • 将文件中的”ContosoUniversity”更改为”Contoso University”。 需要更改三个地方。

  • 添加菜单项StudentsCoursesInstructors,和Department,并删除Contact菜单项。

高亮代码显示所作的变化

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>

    <environment names="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment names="Staging,Production">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>

</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Contoso University</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Students" asp-action="Index">Students</a></li>
                    <li><a asp-area="" asp-controller="Courses" asp-action="Index">Courses</a></li>
                    <li><a asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a></li>
                    <li><a asp-area="" asp-controller="Departments" asp-action="Index">Departments</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2017 - Contoso University</p>
        </footer>
    </div>

    <environment names="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment names="Staging,Production">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

Views/Home/Index.cshtml,将文件的内容替换为以下代码以将有关 ASP.NET 和 MVC 的内容替换为有关此应用程序的内容:

@{
    ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            Contoso University is a sample application that
            demonstrates how to use Entity Framework Core in an
            ASP.NET Core MVC web application.
        </p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
    </div>
</div>

按 CTRL + F5 来运行该项目或从菜单选择调试 > 开始执行不调试。 你会看到首页和将通过这个教程创建的页对应的选项卡。

Contoso 大学主页

Entity Framework Core NuGet 包

若要为项目添加 EF Core 支持,需要安装相应的数据库驱动包。 本教程使用 SQL Server,相关驱动包Microsoft.EntityFrameworkCore.SqlServer。 该包包含在Microsoft.AspNetCore.All 包中,因此不需要手动安装。

此包和其依赖项 (Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Relational) 一起提供 EF 的运行时支持。 你将在之后的迁移教程中学习添加工具包。

有关其他可用于 EF Core 的数据库驱动的信息,请参阅数据库驱动

创建数据模型

接下来你将创建 Contoso 大学应用程序的实体类。 你将从以下三个实体类开始。

课程注册学生数据模型关系图

StudentEnrollment实体之间是一对多的关系,CourseEnrollment实体之间也是一个对多的关系。 换而言之,一名学生可以修读任意数量的课程, 并且某一课程可以被任意数量的学生修读。

接下来,你将创建与这些实体对应的类。

学生实体

学生实体关系图

Models文件夹中,创建一个名为Student.cs的类文件并且将模板代码替换为以下代码。

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

ID属性将成为对应于此类的数据库表中的主键。 默认情况下,EF 将会将名为IDclassnameID的属性解析为主键。

Enrollments属性是导航属性。 导航属性中包含与此实体相关的其他实体。 在这个案例下,Student entity中的Enrollments属性会保留所有与Student实体相关的Enrollment。 换而言之,如果在数据库中有两行描述同一个学生的修读情况 (两行的 StudentID 值相同,而且 StudentID 作为外键和某位学生的主键值相同),Student实体的Enrollments导航属性将包含那两个Enrollment实体。

如果导航属性可以具有多个实体 (如多对多或一对多关系),那么导航属性的类型必须是可以添加、 删除和更新条目的容器,如ICollection<T>。 你可以指定ICollection<T>或实现该接口类型,如List<T>HashSet<T>。 如果指定ICollection<T>,EF在默认情况下创建HashSet<T>集合。

修读实体

修读实体关系图

Models文件夹中,创建Enrollment.cs并且用以下代码替换现有代码:

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

EnrollmentID属性将被设为主键; 此实体使用classnameID模式而不是如Student实体那样直接使用ID。 通常情况下,你选择一个主键模式,并在你的数据模型自始至终使用这种模式。 在这里,使用了两种不同的模式只是为了说明你可以使用任一模式来指定主键。 在后面的教程,你将了解到使用ID这种模式可以更轻松地在数据模型之间实现继承。

Grade属性是enumGrade声明类型后的?表示Grade属性可以为 null。 评级为 null 和评级为零是有区别的 –null 意味着评级未知或者尚未分配。

StudentID属性是一个外键,Student是与其且对应的导航属性。 Enrollment实体与一个Student实体相关联,因此该属性只包含单个Student实体 (与前面所看到的Student.Enrollments导航属性不同后,Student中可以容纳多个Enrollment实体)。

CourseID属性是一个外键,Course是与其对应的导航属性。 Enrollment实体与一个Course实体相关联。

如果一个属性名为<导航属性名><主键属性名>,Entity Framework 就会将这个属性解析为外键属性(例如,Student实体的主键是IDStudentEnrollment的导航属性所以Enrollment实体中StudentID会被解析为外键)。 此外还可以将需要解析为外键的属性命名为<主键属性名>(例如,CourseID由于Course实体的主键所以CourseID也被解析为外键)。

课程实体

课程实体关系图

Models文件夹中,创建Course.cs并且用以下代码替换现有代码:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Enrollments属性是导航属性。 一个Course实体可以与任意数量的Enrollment实体相关。

我们在本系列后面的教程中将会有更多有关DatabaseGenerated特性的例子。 简单来说,此特性让您能自行指定主键,而不是让数据库自动指定主键。

创建数据库上下文

使得给定的数据模型与 Entity Framework 功能相协调的主类是数据库上下文类。 可以通过继承 Microsoft.EntityFrameworkCore.DbContext 类的方式创建此类。 在该类中你可以指定数据模型中包含哪些实体。 你还可以定义某些 Entity Framework 行为。 在此项目中将数据库上下文类命名为SchoolContext

在项目文件夹中,创建名为的文件夹Data

Data文件夹创建名为SchoolContext.cs的类文件,并将模板代码替换为以下代码:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

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

在这里可以省略DbSet<Enrollment>DbSet<Course>语句,实现的功能没有任何改变。 Entity Framework 会隐式包含这两个实体因为Student实体引用了Enrollment实体、Enrollment实体引用了Course实体。

当数据库创建完成后, EF 创建一系列数据表,表名默认和DbSet属性名相同。 集合属性的名称一般使用复数形式,但不同的开发人员的命名习惯可能不一样,开发人员根据自己的情况确定是否使用复数形式。在最后一个 DbSet 属性之后添加以下高亮显示的代码在定义 DbSet 属性的代码之后添加下面高亮代码,对 DbContext 指定单数的表明来覆盖默认的表名。

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

使用依赖注入注册上下文

ASP.NET Core 默认实现依赖注入。 在应用程序启动过程通过依赖注入注册相关服务 (例如 EF 数据库上下文)。 需要这些服务的组件 (如 MVC 控制器) 可以通过向构造函数添加相关参数来获得对应服务。 在本教程后面你将看到控制器构造函数的代码,就是通过上述方式获得上下文实例。

若要将SchoolContext注册为一种服务,打开Startup.cs,并将添加高亮代码添加到ConfigureServices方法中。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<SchoolContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();
}

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

添加using语句引用ContosoUniversity.DataMicrosoft.EntityFrameworkCore命名空间,然后生成项目。

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;

打开appsettings.json文件并添加连接字符串,如下面的示例中所示。

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

SQL Server Express LocalDB

数据库连接字符串指定使用 SQL Server LocalDB 数据库。 LocalDB 是 SQL Server Express 数据库引擎的轻量级版本,用于应用程序开发,不在生产环境中使用。 LocalDB 作为按需启动并在用户模式下运行的轻量级数据库没有复杂的配置。 默认情况下, LocalDB 在C:/Users/<user>目录下创建.mdf数据库文件。

添加代码以使用测试数据初始化数据库

Entity Framework 已经为你创建了一个空数据库。在本部分中,你将编写一个方法用于向数据库填充测试数据,该方法会在数据库创建完成之后执行。

此处将使用EnsureCreated方法来自动创建数据库。 在后面的教程你将了解如何通过使用 Code First Migration 来更改而不是删除并重新创建数据库来处理模型更改。

Data文件夹中,创建名为的新类文件DbInitializer.cs并且将模板代码替换为以下代码,使得在需要时能创建数据库并向其填充测试数据。

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
            new Course{CourseID=1050,Title="Chemistry",Credits=3},
            new Course{CourseID=4022,Title="Microeconomics",Credits=3},
            new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
            new Course{CourseID=1045,Title="Calculus",Credits=4},
            new Course{CourseID=3141,Title="Trigonometry",Credits=4},
            new Course{CourseID=2021,Title="Composition",Credits=3},
            new Course{CourseID=2042,Title="Literature",Credits=4}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

这段代码首先检查是否有学生数据在数据库中,如果没有的话,就可以假定数据库是新建的,然后使用测试数据进行填充。代码中使用数组存放测试数据而不是使用List<T>集合是为了优化性能。

Program.cs,修改Main方法,使得在应用程序启动时能执行以下操作:

  • 从依赖注入容器中获取数据库上下文实例。
  • 调用 seed 方法,将上下文传递给它。
  • Seed 方法完成此操作时释放上下文。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

添加using语句:

using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;

在旧版教程中,你可能会在Startup.cs中的Configure方法看到类似的代码。 我们建议你只在为了设置请求管道时使用Configure方法。 将应用程序启动代码放入Main方法。

现在首次运行该应用程序,创建数据库并使用测试数据作为种子数据。 每当你更改你的数据模型,你可以删除数据库、 更新你的 Initialize 方法,然后使用上述方式更新新数据库。 在之后的教程中,你将了解如何在数据模型更改时,只需修改数据库而无需删除重建数据库。

创建控制器和视图

接下来,将使用 Visual Studio 中的基架引擎添加一个 MVC 控制器和使用 EF 来查询和保存数据的视图。

CRUD 操作方法和视图的自动创建被称为基架。 基架与代码生成不同,基架的代码是一个起点,您可以修改基架以满足自己需求,而你通常无需修改生成的代码。 当你需要自定义生成代码时,你使用一部分类或需求发生变化时重新生成代码。

  • 右键单击解决方案资源管理器中的Controllers文件夹选择添加 > 新搭建的基架项目

如果显示“添加 MVC 依赖项”对话框:

  • 更新 Visual Studio 至最新版本。 当 Visual Studio 版本小于 15.5 时会显示这个对话框

  • 如果更新失败,选择添加, 然后执行添加控制器的步骤。

  • 添加基架的对话框中:

    • 选择视图使用 Entity Framework 的 MVC 控制器

    • 单击添加

  • 添加控制器对话框中:

    • 模型类选择Student

    • 数据上下文类选择SchoolContext

    • 使用StudentsController作为默认名字

    • 单击添加

    基架学生

    当你单击添加后,Visual Studio 基架引擎创建StudentsController.cs文件和一组对应于控制器的视图 (.cshtml文件) 。

(如果你之前手动创建数据库上下文,基架引擎还可以自动创建。 你可以在添加控制器对话框中单击右侧的加号框数据上下文类来指定在一个新上下文类。然后,Visual Studio 将创建你的DbContext,控制器和视图类。)

你会注意到控制器采用SchoolContext作为构造函数参数。

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

ASP.NET 依赖注入机制将会处理传递一个SchoolContext实例到控制器。 在前面的教程中已经通过修改Startup.cs文件来配置注入规则。

控制器包含Index操作方法用于显示数据库中的所有学生。 该方法从学生实体集中获取学生列表,学生实体集则是通过读取数据库上下文实例中的Students属性获得:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

你将在本教程后面了解此代码中的异步编程元素。

Views/Students/Index.cshtml视图使用table标签显示此列表:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    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.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </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>

按 CTRL + F5 来运行该项目或从菜单选择调试 > 开始执行(不调试)

单击学生选项卡以查看DbInitializer.Initialize插入的测试的数据。 你将看到Student选项卡链接在页的顶部或在单击右上角后的导航图标中,具体显示在哪里取决于浏览器窗口宽度。

窄的 Contoso 大学主页
学生索引页

查看数据库

当你启动了应用程序,DbInitializer.Initialize方法调用EnsureCreated。 EF 没有检测到相关数据库然后自己创建了一个,接着Initialize方法的其余代码向数据库中填充数据。 你可以使用 Visual Studio 中的SQL Server 对象资源管理器(SSOX) 查看数据库。

关闭浏览器。

如果 SSOX 窗口尚未打开,请从Visual Studio 中的视图菜单中选择。

在 SSOX 中,单击(localdb) \MSSQLLocalDB > 数据库,然后单击和appsettings.json文件中的连接字符串对应的数据库。

展开节点以查看你的数据库中的表。

在 SSOX 中的表

右键单击Student表,然后单击查看数据若要查看已创建的列和已插入到表的行。

在 SSOX 中学生表

.Mdf.ldf数据库文件位于*C:\Users*文件夹。

因为调用EnsureCreated的初始化方法在启动应用程序时才运行,所以在这之前你可以更改Student类、 删除数据库、 再运行一次应用程序,这时候数据库将自动重新创建,以匹配所做的更改。 例如,如果向Student类添加EmailAddress属性,重新的创建表中会有EmailAddress列。

约定

由于 Entity Framwork 有一定的约束条件,你只需要按规则编写很少的代码就能够创建一个完整的数据库,

  • DbSet类型的属性用作表名。 实体未被DbSet属性引用,实体类名称用作表名称。

  • 实体属性名称用于列名称。

  • ID 或 classnameID 命名的实体属性被识别为主键属性。

  • 如果属性名为 <导航属性名> <主键名>将被解释为外键属性 (例如,StudentID对应Student导航属性,Student实体的主键是ID,所以StudentID被解释为外键属性). 此外也可以将外键属性命名为 <主键属性名> (例如,EnrollmentID,由于Enrollment实体的主键是EnrollmentID,因此被解释为外键)。

约定行为可以被重写。 例如,在本教程前半部分的显式指定表名称, 在本系列后面教程的设置列名称和将任何属性设置为主键或外键。

异步代码

异步编程是 ASP.NET Core 和 EF Core 的默认模式。

Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。 使用同步代码时,可能会出现多个线程被占用但不能执行任何操作的情况,因为它们正在等待 I/O 完成。 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。 因此,异步代码使得服务器更有效地使用资源,并且该服务器可以无延迟地处理更多流量。

异步代码在运行时,会引入的少量开销,在低流量时对性能的影响可以忽略不计,但在针对高流量情况下潜在的性能提升是可观的。

在下面的代码中,async关键字,Task<T>返回值,await关键字,和ToListAsync方法使代码异步执行。

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • async关键字用于告知编译器该方法主体将生成回调并自动创建Task<IActionResult>返回对象。

  • 返回类型Task<IActionResult>表示正在进行的工作返回的结果为IActionResult类型。

  • await关键字会使得编译器将方法拆分为两个部分。 第一部分是以异步方式结束已启动的操作。 第二部分是当操作完成时注入调用回调方法的地方。

  • ToListAsync是由ToList方法的的异步扩展版本。

你使用 Entity Framework 编写异步代码时的一些注意事项:

  • 只有导致查询或发送数据库命令的语句才能以异步方式执行。 包括 ToListAsyncSingleOrDefaultAsync,和SaveChangesAsync。 不包括,操作IQueryable的语句,如var students = context.Students.Where(s => s.LastName == "Davolio")

  • EF 上下文是线程不安全的: 请勿尝试并行执行多个操作。 当调用异步 EF 方法时,始终使用await关键字。

  • 如果你想要利用异步代码的性能优势,请确保你所使用的任何库和包在它们调用导致 Entity Framework 数据库查询方法时也使用异步。
    有关在 .NET 异步编程的详细信息,请参阅异步概述

总结

你现在已创建了一个使用 Entity Framework Core 和 SQL Server Express LocalDB 来存储和显示数据的简单应用程序。 在下一个教程中,你将学习如何执行基本的 CRUD (创建、 读取、 更新、 删除) 操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容