前言:
本系列文章主要為我之前所學知識的一次微小的實踐,以我學校圖書館管理系統為雛形所作。
本系列文章主要參考資料:
微軟文檔:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows
《Pro ASP.NET MVC 5》、《鋒利的 jQuery》
當此系列文章寫完后會在一周內推出修正版。
此系列皆使用 VS2017+C# 作為開發環境。如果有什么問題或者意見歡迎在留言區進行留言。
項目 github 地址:https://github.com/NanaseRuri/LibraryDemo
本章內容:對圖書館系統組成的簡要分析。以及對域模型以及相應數據庫的建立。
知識點:Code First、EF 基本使用方法、ASP.NET Core 使用 EF Core 的配置方法。
一、對圖書館系統域模型的分析
一個圖書館系統需要有管理員、 學生、書架以及書籍

域模型,即用來存儲數據的模型。
在此域模型可以用以下結構創建:

二、項目結構
然后就可以開始建立該項目了:

Docker支持和身份驗證在以后可以自行添加,在此就不使用相應的支架特性。
所謂支架特性就是 VS2017 能夠自動為我們完成一系列的工作的特性,當然這部分工作也可以自行完成。
創建一個單元測試項目並引用 LibraryDemo 為以后的單元測試做准備。



然后正式開始圖書館項目的編寫:
為了辨識,我創建了這樣的文件夾結構,這里的 Migrations 文件夾由后面提到的 EF 自動創建。

三、建立域模型
學位枚舉:
1 public enum Degrees
2 {
3 CollegeStudent,
4 Postgraduate,
5 DoctorateDegree
6 }
圖書借閱狀態枚舉:
1 public enum BookState 2 { 3 /// <summary> 4 /// 可借閱 5 /// </summary> 6 [Display(Name = "正常")] 7 Normal, 8 9 /// <summary> 10 /// 館內閱覽 11 /// </summary> 12 [Display(Name = "館內閱覽")] 13 Readonly, 14 15 /// <summary> 16 /// 已借出 17 /// </summary> 18 [Display(Name = "已借出")] 19 Borrowed, 20 21 /// <summary> 22 /// 被續借 23 /// </summary> 24 [Display(Name = "被續借")] 25 ReBorrowed, 26 27 /// <summary> 28 /// 被預約 29 /// </summary> 30 [Display(Name = "被預約")] 31 Appointed, 32 33 [Display(Name = "過期")] 34 Expired 35 }
學生信息:
1 public class Student
2 {
3 [Key]
4 public string Id { get; set; }
5 [Required]
6 public string Name { get; set; }
7
8 /// <summary>
9 /// 學位,用來限制借書數目
10 /// </summary>
11 [Required]
12 public Degrees Degree { get; set; }
13
14 /// <summary>
15 /// 最大借書數目
16 /// </summary>
17 [Required]
18 public int MaxBooksNumber { get; set; }
19
20 /// <summary>
21 /// 已借圖書
22 /// </summary>
23 public IEnumerable<Book> KeepingBooks { get; set; }
24
25 /// <summary>
26 /// 預約的書
27 /// </summary>
28 public string AppointingBookBarCode { get; set; }
29
30 /// <summary>
31 /// 罰款
32 /// </summary>
33 public decimal Fine { get; set; }
34 }
在約定中,若不指定主鍵,則 EF 會使用 (類名)+ID 的方式指定或創建主鍵,在此使用 [Key] 指定主鍵,使用 [Required] 指定字段為必須,這種可以為屬性添加在數據庫中的約束或者在視圖中的約束的修飾稱為 DataAnnotations 。
借閱書籍信息:
1 /// <summary>
2 /// 用於借閱的書籍信息
3 /// </summary>
4 public class Book
5 {
6 /// <summary>
7 /// 條形碼
8 /// </summary>
9 [Key][Required]
10 public string BarCode { get; set; }
11
12 public string ISBN { get; set; }
13
14 /// <summary>
15 /// 書名
16 /// </summary>
17 [Required]
18 public string Name { get; set; }
19
20 /// <summary>
21 /// 取書號
22 /// </summary>
23 public string FetchBookNumber { get; set; }
24
25 /// <summary>
26 /// 所在書架
27 /// </summary>
28 //public Bookshelf Bookshelf { get; set; }
29 public ICollection<BookMiddle> BookMiddles { get; set; }
30
31 /// <summary>
32 /// 借出時間
33 /// </summary>
34 public DateTime? BorrowTime { get; set; }
35
36 /// <summary>
37 /// 到期時間
38 /// </summary>
39 public DateTime? MatureTime { get; set; }
40
41 /// <summary>
42 /// 預約最晚借書日期
43 /// </summary>
44 public DateTime? AppointedLatestTime { get; set; }
45
46 /// <summary>
47 /// 借閱狀態
48 /// </summary>
49 public BookState State { get; set; }
50
51 /// <summary>
52 /// 持有者,指定外鍵
53 /// </summary>
54 public Student Keeper { get; set; }
55 }
書籍詳細信息:
1 /// <summary> 2 /// 書籍自身的詳細信息 3 /// </summary> 4 public class BookDetails 5 { 6 [Key] 7 public string ISBN { get; set; } 8 [Required] 9 public string Name { get; set; } 10 [Required] 11 public string Author { get; set; } 12 [Required] 13 public string Press { get; set; } 14 15 /// <summary> 16 /// 出版時間 17 /// </summary> 18 [Required] 19 public DateTime PublishDateTime{ get; set; } 20 21 /// <summary> 22 /// 書籍版本 23 /// </summary> 24 [Required] 25 public int Version { get; set; } 26 27 /// <summary> 28 /// 載體形態,包括頁數、媒介等信息 29 /// </summary> 30 public string SoundCassettes { get; set; } 31 32 /// <summary> 33 /// 簡介 34 /// </summary> 35 public string Description { get; set; } 36 }
書架信息:
1 public class Bookshelf
2 {
3 /// <summary>
4 /// 書架ID
5 /// </summary>
6 [Key]
7 public int BookshelfId { get; set; }
8
9 /// <summary>
10 /// 書架的書籍類別
11 /// </summary>
12
13 [Required]
14 public string Sort { get; set; }
15 /// <summary>
16 /// 最小取書號
17 /// </summary>
18 [Required]
19 public string MinFetchNumber { get; set; }
20 [Required]
21 public string MaxFetchNumber { get; set; }
22
23 /// <summary>
24 /// 書架位置
25 /// </summary>
26 [Required]
27 public string Location { get; set; }
28
29 /// <summary>
30 /// 全部藏書
31 /// </summary>
32 public ICollection<Book> Books { get; set; }
33 }
推薦書籍信息的結構與書籍詳細信息一致:
1 public class RecommendedBook
2 {
3 [Key]
4 public string ISBN { get; set; }
5 [Required]
6 public string Name { get; set; }
7 [Required]
8 public string Author { get; set; }
9 [Required]
10 public string Press { get; set; }
11
12 /// <summary>
13 /// 出版時間
14 /// </summary>
15 [Required]
16 public DateTime PublishDateTime { get; set; }
17
18 /// <summary>
19 /// 書籍版本
20 /// </summary>
21 [Required]
22 public int Version { get; set; }
23
24 /// <summary>
25 /// 載體形態,包括頁數、媒介等信息
26 /// </summary>
27 public string SoundCassettes { get; set; }
28 }
登錄所用信息(此處 Student 模型使用 ASP.NET Core 內建的 Identity,而 Admin 使用自己建立的邏輯):
1 public class Student:IdentityUser
2 {
3 /// <summary>
4 /// 學號
5 /// </summary>
6 [ProtectedPersonalData]
7 [RegularExpression("[UI]\\d{9}")]
8 public override string UserName { get; set; }
9
10 [Required]
11 public string Name { get; set; }
12
13 /// <summary>
14 /// 學位,用來限制借書數目
15 /// </summary>
16 [Required]
17 public Degrees Degree { get; set; }
18
19 /// <summary>
20 /// 最大借書數目
21 /// </summary>
22 [Required]
23 public int MaxBooksNumber { get; set; }
24
25 /// <summary>
26 /// 已借圖書
27 /// </summary>
28 public IEnumerable<Book> KeepingBooks { get; set; }
29
30 /// <summary>
31 /// 預約的書
32 /// </summary>
33 public string AppointingBookBarCode { get; set; }
34
35 [StringLength(14,MinimumLength = 11)]
36 public override string PhoneNumber { get; set; }
37
38 /// <summary>
39 /// 罰款
40 /// </summary>
41 public decimal Fine { get; set; }
42 }
1 public class Admin
2 {
3 [Key]
4 public string UserName { get; set; }
5
6 [Required]
7 public string PhoneNumber { get; set; }
8
9 [Required]
10 public string Email { get; set; }
11
12 [Required]
13 public string Password { get; set; }
14 }
四、創建 DbContext
根據約定,創建 DbContext 類為 EF 提供建立的數據庫的結構:
1 public class StudentIdentityDbContext:IdentityDbContext<Student>
2 {
3 public StudentIdentityDbContext(DbContextOptions<StudentIdentityDbContext> options) : base(options)
4 {
5 }
6 }
1 public class AdminDbContext:DbContext
2 {
3 public AdminDbContext(DbContextOptions<AdminDbContext> options) : base(options)
4 {
5 }
6
7 public DbSet<Admin> Admins { get; set; }
8 }
1 public class LendingInfoDbContext:DbContext
2 {
3 public LendingInfoDbContext(DbContextOptions<LendingInfoDbContext> options) : base(options)
4 {
5 }
6
7 public DbSet<Book> Books { get; set; }
8 public DbSet<BookDetails> BooksDetail { get; set; }
9 public DbSet<Bookshelf> Bookshelves { get; set; }
10 public DbSet<Student> Students { get; set; }
11 public DbSet<RecommendedBook> RecommendedBooks { get; set; }
12 }
每個 DbContext 類代表一個數據庫,每個 DbSet<T> 代表一張表。而構造函數參數以及其形式為 ASP.NET Core 的依賴注入的約定形式。
五、根據約定配置數據庫,進行依賴注入
在 appsettings.json 中添加數據庫連接字符串。
{
"ConnectionStrings": {
"AdminDbContext": "Server=(localdb)\\mssqllocaldb;Database=AdminDbContext;Trusted_Connection=True;MultipleActiveResultSets=true",
"LendingInfoDbContext": "Server=(localdb)\\mssqllocaldb;Database=LendingInfoDbContext;Trusted_Connection=True;MultipleActiveResultSets=true",
"StudentIdentityDbContext": "Server=(localdb)\\mssqllocaldb;Database=StudentIdentityDbContext;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
在 Startup.cs 中的 ConfigureServices 方法中對數據庫進行配置:
1 services.AddDbContext<LendingInfoDbContext>(options =>
2 {
3 options.UseSqlServer(Configuration.GetConnectionString("LendingInfoDbContext"));
4 });
5 services.AddDbContext<AdminDbContext>(options =>
6 {
7 options.UseSqlServer(Configuration.GetConnectionString("AdminDbContext"));
8 });
9 services.AddDbContext<StudentIdentityDbContext>(options =>
10 {
11 options.UseSqlServer(Configuration.GetConnectionString("StudentIdentityDbContext"));
12 });
六、數據庫的遷移、創建及更新
然后在 pm控制台 中添加遷移:
添加遷移的語法為 add-migration <遷移類名> -c <具體 DbContext 名>
分別執行以下代碼:
1 cd LibraryDemo 2 add-migration Admin -c LibraryDemo.Data.AdminDbContext 3 add-migration LendingInfo -c LibraryDemo.Data.LendingInfoDbContext 4 add-migration StudentIdentity -c LibraryDemo.Data.StudentIdentityDbContext



運行 add-migration 命令會創建 Migrations 文件夾以及相應的遷移快照,此處的 AddSomeDetails 和 AddRequired 為后來我自己添加的內容:

顯示的類名為 <創建時間>_<遷移類名>,而實際的類名為 add-migration 后的第一個參數名。

在創建遷移時,EF 會自動為我們創建或更新對應 DbContext 的快照,即其中后綴為 Snapshot 的類。其中會包含當前對應的 DbCOntext 的結構,並會以代碼保留相應的約束,如 LendingInfoDbContextModelSnapshot 類:


生成的遷移類 LendingInfo 和 Account 類則有兩個方法—— 用於更新數據庫的 Up 方法和用以回溯數據庫的 Down 方法,可以在這兩個方法或者在快照的 BuildModel 方法中使用 Fluent API 對數據庫做進一步的改動,並且通過對 Fluent API 的使用可以使我們的類少用 DataAnnotations 以保證類的整潔。
需要注意的是,生成的遷移類中的 Up 和 Down 方法是根據生成遷移之前的數據庫快照生成的,如我在之后為 LendingInfoDbContext 添加 DbSet<RecommendedBook> 時,在以上的基礎上運行了 add-migration AddRecommendedBook -c LibraryDemo.Data.LendingInfoDbContext ,生成的 Up 方法只包括添加表 RecommendedBooks 的行為,而 Down 方法只包括刪除表 RecommendedBooks 的行為。

隨后在 pm控制台 執行以下創建或更新數據庫:
1 update-database -c LibraryDemo.Data.AdminDbContext 2 update-database -c LibraryDemo.Data.LendingInfoDbContext 3 update-database -c LibraryDemo.Data.StudentIdentityDbContext
最后在 SQL server對象管理器 中可以看見創建的數據庫以及對應的表:


至此域模型創建工作完成。
補充:
使用命令行對數據庫進行遷移及更新有兩種方式:
1 dotnet ef migrations migrationName -c TargetContext 2 dotnet ef database update -c TargetContext
1 add-migration migrationName -c TargetContext
2 update-Database -c TargetContext
windows 命令行命令不區分大小寫,其中 migrationName 為遷移類名,最好提供有意義的命名;而 TargetContext 為目標 DbContext 類名,需要使用帶有命名空間的完全命名。
如果需要刪除數據庫則使用 drop 方法
drop-database -c TargetContext
而為 update 方法指定遷移類則可以回溯數據庫。
Update-Database LendingInfoDbContext -TargetMigration:"20181127081115_LendingInfo.cs"

