前言:
本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。
本系列文章主要参考资料:
微软文档: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"