Contoso University 示例 Web 應用演示了如何使用 Entity Framework (EF) Core 創(chuàng)建 ASP.NET Core Razor Pages 應用。
該示例應用是一個虛構(gòu)的 Contoso University 的網(wǎng)站。 其中包括學生錄取、課程創(chuàng)建和講師分配等功能。 本頁是介紹如何構(gòu)建 Contoso University 示例應用系列教程中的第一部分。
具有以下工作負載的 Visual Studio 2017 15.7.3 版或更高版本:
熟悉 Razor 頁面。 新程序員在開始學習本系列之前,應先完成 Razor 頁面入門。
如果遇到無法解決的問題,可以通過與 已完成的項目對比代碼來查找解決方案。 獲取幫助的一個好方法是將問題發(fā)布到適用于 ASP.NET Core 或 EF Core 的 StackOverflow.com。
這些教程中所構(gòu)建的應用是一個基本的大學網(wǎng)站。
用戶可以查看和更新學生、課程和講師信息。 以下是在本教程中創(chuàng)建的幾個屏幕。
此網(wǎng)站的 UI 樣式與內(nèi)置模板生成的 UI 樣式類似。 教程的重點是 EF Core 和 Razor 頁面,而非 UI。
有關(guān)上述步驟的圖像,請參閱創(chuàng)建 Razor Web 應用。 運行應用。
設(shè)置網(wǎng)站菜單、布局和主頁時需作少量更改。 進行以下更改以更新 Pages/Shared/_Layout.cshtml:
突出顯示所作更改。 (所有標記均不顯示。)
HTML
<!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 include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" rel="external nofollow" target="_blank"
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-page="/Index" class="navbar-brand">Contoso University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Students/Index">Students</a></li>
<li><a asp-page="/Courses/Index">Courses</a></li>
<li><a asp-page="/Instructors/Index">Instructors</a></li>
<li><a asp-page="/Departments/Index">Departments</a></li>
</ul>
</div>
</div>
</nav>
<partial name="_CookieConsentPartial" />
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2018 : Contoso University</p>
</footer>
</div>
@*Remaining markup not shown for brevity.*@
在 Pages/Index.cshtml 中,將文件內(nèi)容替換為以下代碼,以將有關(guān) ASP.NET 和 MVC 的文本替換為有關(guān)本應用的文本:
HTML
@page
@model IndexModel
@{
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 Razor Pages web app.
</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"
rel="external nofollow" target="_blank" >
See the tutorial »
</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="http://m.hgci.cn/targetlink?url=https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-rp/intro/samples/cu-final">
See project source code »
</a>
</p>
</div>
</div>
創(chuàng)建 Contoso University 應用的實體類。 從以下三個實體開始:
Student 和 Enrollment 實體之間存在一對多關(guān)系。 Course 和 Enrollment 實體之間存在一對多關(guān)系。 一名學生可以報名參加任意數(shù)量的課程。 一門課程中可以包含任意數(shù)量的學生。
以下部分將為這幾個實體中的每一個實體創(chuàng)建一個類。
創(chuàng)建 Models 文件夾。 在 Models 文件夾中,使用以下代碼創(chuàng)建一個名為 Student.cs 的類文件:
C#
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 屬性成為此類對應的數(shù)據(jù)庫 (DB) 表的主鍵列。 默認情況下,EF Core 將名為 ID 或 classnameID 的屬性視為主鍵。 在 classnameID 中,classname 為類名稱。 另一種自動識別的主鍵是上例中的 StudentID。
Enrollments 屬性是導航屬性。 導航屬性鏈接到與此實體相關(guān)的其他實體。 在這種情況下,Student entity 的 Enrollments 屬性包含與該 Student 相關(guān)的所有 Enrollment 實體。 例如,如果數(shù)據(jù)庫中的 Student 行有兩個相關(guān)的 Enrollment 行,則 Enrollments 導航屬性包含這兩個 Enrollment 實體。 相關(guān)的 Enrollment 行是 StudentID 列中包含該學生的主鍵值的行。 例如,假設(shè) ID=1 的學生在 Enrollment 表中有兩行。 Enrollment 表中有兩行的 StudentID = 1。 StudentID 是 Enrollment 表中的外鍵,用于指定 Student 表中的學生。
如果導航屬性包含多個實體,則導航屬性必須是列表類型,例如 ICollection<T>。 可以指定 ICollection<T> 或諸如 List<T> 或 HashSet<T> 的類型。 使用 ICollection<T> 時,EF Core 會默認創(chuàng)建 HashSet<T> 集合。 包含多個實體的導航屬性來自于多對多和一對多關(guān)系。
在 Models 文件夾中,使用以下代碼創(chuàng)建 Enrollment.cs:
C#
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
EnrollmentID 屬性為主鍵。 Student 實體使用的是 ID 模式,而本實體使用的是 classnameID 模式。 通常情況下,開發(fā)者會選擇一種模式并在整個數(shù)據(jù)模型中都使用該模式。 下一個教程將介紹如何使用不帶類名的 ID,以便更輕松地在數(shù)據(jù)模型中實現(xiàn)集成。
Grade 屬性為 enum。 Grade 聲明類型后的?表示 Grade 屬性可以為 null。 評級為 null 和評級為零是有區(qū)別的 --null 意味著評級未知或者尚未分配。
StudentID 屬性是外鍵,其對應的導航屬性為 Student。 Enrollment 實體與一個 Student 實體相關(guān)聯(lián),因此該屬性只包含一個 Student 實體。 Student 實體與 Student.Enrollments 導航屬性不同,后者包含多個 Enrollment 實體。
CourseID 屬性是外鍵,其對應的導航屬性為 Course。 Enrollment 實體與一個 Course 實體相關(guān)聯(lián)。
如果屬性命名為 <navigation property name><primary key property name>,EF Core 會將其視為外鍵。例如 Student 導航屬性的 StudentID,因為 Student 實體的主鍵為 ID。 還可以將外鍵屬性命名為 <primary key property name>。 例如 CourseID,因為 Course 實體的主鍵為 CourseID。
在 Models 文件夾中,使用以下代碼創(chuàng)建 Course.cs:
C#
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 實體可與任意數(shù)量的 Enrollment 實體相關(guān)。
應用可以通過 DatabaseGenerated 特性指定主鍵,而無需靠數(shù)據(jù)庫生成。
本部分將為“學生”模型搭建基架。 確切地說,基架工具將生成頁面,用于對“學生”模型執(zhí)行創(chuàng)建、讀取、更新和刪除 (CRUD) 操作。
完成“使用實體框架(CRUD)添加 Razor Pages”對話框:
如果對前面的步驟有疑問,請參閱搭建“電影”模型的基架。
搭建基架的過程會創(chuàng)建并更改以下文件:
ASP.NET Core 通過依賴關(guān)系注入進行生成。 服務(例如 EF Core 數(shù)據(jù)庫上下文)在應用程序啟動期間通過依賴關(guān)系注入進行注冊。 需要這些服務(如 Razor 頁面)的組件通過構(gòu)造函數(shù)提供相應服務。 本教程的后續(xù)部分介紹了用于獲取數(shù)據(jù)庫上下文實例的構(gòu)造函數(shù)代碼。
基架工具自動創(chuàng)建 DB 上下文并將其注冊到依賴關(guān)系注入容器。
在 Startup.cs 中檢查 ConfigureServices 方法。 基架添加了突出顯示的行:
C#
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_1);
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}
通過調(diào)用 DbContextOptions 對象中的一個方法將連接字符串名稱傳遞到上下文。 進行本地開發(fā)時, ASP.NET Core 配置系統(tǒng) 在 appsettings.json 文件中讀取數(shù)據(jù)庫連接字符串。
在 Program.cs 中,修改 Main 方法以執(zhí)行以下操作:
下面的代碼顯示更新后的 Program.cs 文件。
C#
using ContosoUniversity.Models; // SchoolContext
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; // CreateScope
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
EnsureCreated 確保存在上下文數(shù)據(jù)庫。 如果存在,則不需要任何操作。 如果不存在,則會創(chuàng)建數(shù)據(jù)庫及其所有架構(gòu)。 EnsureCreated 不使用遷移創(chuàng)建數(shù)據(jù)庫。 使用 EnsureCreated 創(chuàng)建的數(shù)據(jù)庫稍后無法使用遷移更新。
啟動應用時會調(diào)用 EnsureCreated,以進行以下工作流:
架構(gòu)快速演變時,在開發(fā)初期使用 EnsureCreated 很方便。 本教程后面將刪除 DB 并使用遷移。
運行應用并接受 cookie 策略。 此應用不保留個人信息。 有關(guān) cookie 策略的信息,請參閱歐盟一般數(shù)據(jù)保護條例 (GDPR) 支持。
數(shù)據(jù)庫上下文類是為給定數(shù)據(jù)模型協(xié)調(diào) EF Core 功能的主類。 數(shù)據(jù)上下文派生自 Microsoft.EntityFrameworkCore.DbContext。 數(shù)據(jù)上下文指定數(shù)據(jù)模型中包含哪些實體。 在此項目中將數(shù)據(jù)庫上下文類命名為 SchoolContext。
使用以下代碼更新 SchoolContext.cs:
C#
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Student { get; set; }
public DbSet<Enrollment> Enrollment { get; set; }
public DbSet<Course> Course { get; set; }
}
}
突出顯示的代碼為每個實體集創(chuàng)建 DbSet<TEntity> 屬性。 在 EF Core 術(shù)語中:
可以省略 DbSet<Enrollment> 和 DbSet<Course>。 EF Core 隱式包含了它們,因為 Student 實體引用 Enrollment 實體,而 Enrollment 實體引用 Course 實體。 在本教程中,將 DbSet<Enrollment>和 DbSet<Course> 保留在 SchoolContext 中。
連接字符串指定 SQL Server LocalDB。 LocalDB 是輕型版本 SQL Server Express 數(shù)據(jù)庫引擎,專門針對應用開發(fā),而非生產(chǎn)使用。 LocalDB 作為按需啟動并在用戶模式下運行的輕量級數(shù)據(jù)庫沒有復雜的配置。 默認情況下,LocalDB 會在 C:/Users/<user> 目錄中創(chuàng)建 .mdf 數(shù)據(jù)庫文件。
EF Core 會創(chuàng)建一個空的數(shù)據(jù)庫。 本部分中編寫了 Initialize 方法來使用測試數(shù)據(jù)填充該數(shù)據(jù)庫。
在 Data 文件夾中,新建一個名為 DbInitializer.cs 的類文件,并添加以下代碼:
C#
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Models
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// context.Database.EnsureCreated();
// Look for any students.
if (context.Student.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.Student.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.Course.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.Enrollment.Add(e);
}
context.SaveChanges();
}
}
}
注意:上面的代碼對命名空間使用 Models (namespace ContosoUniversity.Models),而不是 Data。 Models 與基架生成的代碼一致。 有關(guān)詳細信息,請參閱此 GitHub 基架問題。
該代碼會檢查數(shù)據(jù)庫中是否存在任何學生。 如果 DB 中沒有任何學生,則會使用測試數(shù)據(jù)初始化該 DB。 代碼中使用數(shù)組存放測試數(shù)據(jù)而不是使用 List<T> 集合是為了優(yōu)化性能。
EnsureCreated 方法自動為數(shù)據(jù)庫上下文創(chuàng)建數(shù)據(jù)庫。 如果數(shù)據(jù)庫已存在,則返回 EnsureCreated,并且不修改數(shù)據(jù)庫。
在 Program.cs 中,將 Main 方法修改為調(diào)用 Initialize:
C#
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
// using ContosoUniversity.Data;
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
刪除任何學生記錄并重啟應用。 如果未初始化 DB,則在 Initialize 中設(shè)置斷點以診斷問題。
從 Visual Studio 中的“視圖”菜單打開 SQL Server 對象資源管理器 (SSOX)。 在 SSOX 中,單擊“(localdb)\MSSQLLocalDB”>“數(shù)據(jù)庫”>“ContosoUniversity1”。
展開“表”節(jié)點。
右鍵單擊 Student 表,然后單擊“查看數(shù)據(jù)”,以查看創(chuàng)建的列和插入到表中的行。
異步編程是 ASP.NET Core 和 EF Core 的默認模式。
Web 服務器的可用線程是有限的,而在高負載情況下的可能所有線程都被占用。 當發(fā)生這種情況的時候,服務器就無法處理新請求,直到線程被釋放。 使用同步代碼時,可能會出現(xiàn)多個線程被占用但不能執(zhí)行任何操作的情況,因為它們正在等待 I/O 完成。 使用異步代碼時,當進程正在等待 I/O 完成,服務器可以將其線程釋放用于處理其他請求。 因此,使用異步代碼可以更有效地利用服務器資源,并且可以讓服務器在沒有延遲的情況下處理更多流量。
異步代碼會在運行時引入少量開銷。 流量較低時,對性能的影響可以忽略不計,但流量較高時,潛在的性能改善非常顯著。
在以下代碼中,async 關(guān)鍵字和 Task<T> 返回值,await 關(guān)鍵字和 ToListAsync 方法讓代碼異步執(zhí)行。
C#
public async Task OnGetAsync()
{
Student = await _context.Student.ToListAsync();
}
編寫使用 EF Core 的異步代碼時需要注意的一些事項:
有關(guān) .NET 中異步編程的詳細信息,請參閱異步概述和使用 Async 和 Await 的異步編程。
下一個教程將介紹基本的 CRUD(創(chuàng)建、讀取、更新、刪除)操作。
更多建議: