第1课 创建MVC项目,使用EF初始化数据库
文章提纲
-
概述
-
核心概念介绍
-
从空白开始,建立一个基本框架详细步骤
概述
-
本系列文章及文章中的例子主要基于微软官方文档
-
使用工具 : VS2019 + SQL Server 2019
-
开始主要讲解MVC + EF搭配使用
核心概念介绍
MVC,Model – View – Controller 的简写
Model 封装业务逻辑相关的数据及对数据的处理方法
View 向用户提供交互界面
Controller 负责控制Model和View
Entity Framework(EF)是一个对象关系映射器,它让.Net开发人员可以使用领域对象来操作关系数据,并帮助开发人员省略大部分访问数据的代码。
在介绍如何使用EF之前,先了解一下EF的几种工作流,它们如下图所示:
● 模型优先(创建一个新的数据库):
○ 在设计器中创建模型。
○ 通过模型创建数据库。
○ 通过模型自动生成实体类。
● 数据库优先(使用已存在的数据库):
○ 在设计器中反向工程模型。
○ 通过模型自动生成实体类。
● 代码优先(新数据库):
○ 在代码中定义实体类和映射(如类和表的映射、属性和表列的映射)。
○ 通过模型创建数据库。
○ 使用Migrations去更新数据库(结构)。
● 代码优先(使用已存在数据库):
○ 在代码中定义实体类和映射(如类和表的映射、属性和表列的映射)。
○ 或者使用反向工程生成实体类和映射。
建立一个基本框架的详细步骤
1.新建项目
打开Global.asax, 我们发现程序启动的时候注册了路由规则:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// 程序启动的时候注册了路由规则 RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } }
打开RouteConfig.cs文件,注意到里面有个静态方法,这就是映射路由的控制,这个方法定义了路由规则,其中:url: "{controller}/{action}/{id}"定义了URL的格式:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
2.创建Controller
右键Controllers文件夹,这里先建一个AccountController,添加后会发现多了下图方框处的类和文件夹
打开新建的AccountController.cs看下,自动生成了一个方法:
public ActionResult Index() { return View(); }
3.创建View
添加View有两种方法,一种是直接在Views文件夹下添加;另外一种是通过Controller中的Action来添加,打开AccountController, 右键Index方法,添加视图;这样就添加了一个和特定的Controller和Action(这里指AccountController和Index)相对应的View(ViewsàAccountàIndex.cshtml)。
按照上面的方法,我们创建用户注册/登录的两个控制器,对应生成对应的View:
/// <summary> /// 登陆页面 /// </summary> /// <returns></returns> public ActionResult Login() { return View(); } /// <summary> /// "HttpPost":这个Action只会接受http post请求 /// </summary> /// <param name="fc"></param> /// <returns></returns> [HttpPost] public ActionResult Login(FormCollection fc) { return View(); } /// <summary> /// 注册页面 /// </summary> /// <returns></returns> public ActionResult Register() { return View(); }
新添加的Action中增加了一个[HttpPost] ,表示这个Action只会接受http post请求。ASP.NET MVC提供了Action Method Selector, HttpPost就是其中之一。HttpPost属性典型的应用场景:
涉及到需要接受客户端窗口数据的时候,创建一个用于接收HTTP Get请求的Action, 用于显示界面, 提供给用户填写数据;
另一个同名Action则应用[HttpPost]属性,用于接收用户发来的数据,完成对应的功能。
4.创建相关类 (Data Model)
Models文件夹里面存放对应于数据库表的实体,为了更加贴近真实情况,我们针对用户建立三个相关的类(SysUser, SysRole, SysUserRole),用户表和角色表是多对多的关系:
用户表实体SysUser
/// <summary> /// 用户信息 /// </summary> public class SysUser { /// <summary> /// 用户ID /// </summary> public int ID { get; set; } /// <summary> /// 用户名 /// </summary> public string UserName { get; set; } /// <summary> /// 密码 /// </summary> public string Password { get; set; } /// <summary> /// Email /// </summary> public string Email { get; set; } public virtual ICollection<SysUserRole> SysUserRoles { get; set; } }
角色表实体SysRole
/// <summary> /// 用户角色 /// </summary> public class SysRole { /// <summary> /// 主键ID /// </summary> public int ID { get; set; } /// <summary> /// 角色名称 /// </summary> public string RoleName { get; set; } /// <summary> /// 角色描述 /// </summary> public string RoleDesc { get; set; } public virtual ICollection<SysUserRole> SysUserRoles { get; set; } }
用户角色关系表实体SysUserRole
/// <summary> /// 用户与角色关系 /// </summary> public class SysUserRole { /// <summary> /// 主键ID /// </summary> public int ID { get; set; } /// <summary> /// 用户ID /// </summary> public int SysUserID { get; set; } /// <summary> /// 角色ID /// </summary> public int SysRoleID { get; set; } public virtual SysUser SysUser { get; set; } public virtual SysRole SysRole { get; set; } }
对于上面几个类的约定和说明:
-
EF生成数据库时,ID 属性将会成为主键。(约定:EF默认会将ID或classnameID生成主键, MSDN建议保持风格的一致性, 都用ID或classnameID, 我们这里都用ID)
-
EF 生成数据库时 , <navigation property name><primary key property name>这种形式的会成为外键. ( 约定 )
例如外键 SysUserID = SysUser(navigation property)+ID(SysUser的主键)
-
定义为virtual的几个属性是 navigation 属性(virtual非必须, 只是惯例用法, 后面文章将会讲解用virtual的好处).
navigation 属性保存着其他的关联entity(entities)
示例中, SysUser和SysUserRole是一对多的关系, SysRole和SysUserRole也是一对多的关系.
如果是 "多", 属性类型就必须是list( 这里用的是Icollection )
5.创建Database Context
前置条件:安装EF
打开 工具》库程序包管理器》程序包管理器控制台,输入 install-package entityframework。EF的架构图如下:
从上图可以看出,EF框架在底层是通过调用ADO.NET来实现数据库操作的。
在DAL文件夹下创建类 AccountContext.cs , 让他继承自System.Data.Entity.DbContext, 我们用这个类完成EF的功能。
主要做下面三件事:
-
为每个entity set创建一个DbSet
在EF中,通常情况下一个entity set对应数据库中的一张表,一个entity对应表中的一行。
-
指定一个连接字符串
构造函数中的 base("AccountContext") 。
默认情况下和类名一样,即AccountContext,我们显式的给他指定出来。
-
指定单数形式的表名
默认情况下会生成复数形式的表,如SysUsers
表名用单复数形式看各自的习惯,没有明确的规定。有的公司表名全用单数,有的公司根据表的意思,有单数也有复数。
/// <summary> /// 在EF中,通常情况下一个entity set对应数据库中的一张表,一个entity对应表中的一行 /// </summary> public class AccountContext : DbContext { public AccountContext() : base("AccountContext") { //指定一个连接字符串,默认情况下和类名一样,即AccountContext,我们显式的给他指定出来 } public DbSet<SysUser> SysUsers { get; set; } public DbSet<SysRole> SysRoles { get; set; } public DbSet<SysUserRole> SysUserRoles { get; set; } /// <summary> /// 默认情况下会生成复数形式的表,如SysUsers,我们这里指定单数形式的表名,如SysUser /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }
配合上面第2点,先把web.config中连接字符串给指定了:
<connectionStrings> <!--AttachDBFilename=|DataDirectory|MVCDemo.mdf设定了数据库文件的存放位置:在项目根目录的App_Data文件夹下--> <add name="AccountContext" connectionString="Data Source=.;database=MvcDemo;uid=sa;pwd=123456;AttachDBFilename=|DataDirectory|MvcDemo.mdf;"
providerName="System.Data.SqlClient"/> </connectionStrings>
5.创建Initializer,使用EF初始化数据库,插入示例数据
EF可以以多种方式建立数据库。
我们采用如下方式:
第一次运行程序时新建数据库,插入测试数据; model改变(和database不一致)时删除重建数据库,插入测试数据。
目前在开发阶段,不用管数据丢失的问题,直接drop and re-create比较方便。
等系列文章结束后会讲解生产环境中如何不丢失数据修改schema
下面我们就新建类AccountInitializer.cs来完成这个工作。
public class AccountInitializer : DropCreateDatabaseIfModelChanges<AccountContext> { protected override void Seed(AccountContext context) { /** 第一次运行程序时新建数据库,插入测试数据; model改变(和database不一致)时删除重建数据库,插入测试数据。 目前在开发阶段,不用管数据丢失的问题,直接drop and re - create比较方便。 */ var sysUsers = new List<SysUser> { new SysUser{UserName="张三",Password="1",Email="666666@qq.com"}, new SysUser{UserName="李四",Password="2",Email="888888@qq.com"} }; sysUsers.ForEach(s => context.SysUsers.Add(s)); context.SaveChanges(); var sysRoles = new List<SysRole> { new SysRole{RoleName="管理员",RoleDesc="具有所有权限"}, new SysRole{RoleName="一般用户",RoleDesc="只有部分权限"} }; sysRoles.ForEach(s => context.SysRoles.Add(s)); context.SaveChanges(); } }
Seed方法用我们之前定义的database context(即AccountContext) 作为参数,通过这个context将entities添加到database中去。(就是我们前面说的桥梁作用)
从上面代码可以看出, Seed方法对每一个entity的类型(我们用了SysUser和SysRole, SysUserRole我们暂不添加):
创建一个colletion 》 添加到适当的 DbSet property 》保存到数据库。
修改web.config, 找到entityFramework配置节,添加context节点,配置刚刚写好的initializer类,context 配置节中, type 的值对应 (context class的完整描述,程序集),databaseInitializer 配置节中 , type 的值对应 (initializer class 的完整描述,程序集), 如果你不想EF使用某个context, 可以将下面方框处设置为true。
<entityFramework> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> <contexts> <!--type 的值对应 (context class的完整描述,程序集),如果你不想EF使用某个context, 可以将disableDatabaseInitialization设置为true--> <context type="MvcWebDemo.DAL.AccountContext,MvcWebDemo" > <!--type 的值对应 (initializer class 的完整描述,程序集)--> <databaseInitializer type="MvcWebDemo.DAL.AccountInitializer,MvcWebDemo"></databaseInitializer> </context> </contexts> </entityFramework>
现在EF一切就绪,运行程序,当第一次连接数据库时,EF比较model(AccountContext和entity classes) 和database. 如果两边不一致,程序将会drop and re-create数据库。
检查一下数据库部分是否符合我们的预期:
打开数据库,发现MVCDemo这个数据库已经新建,示例数据已经插入
打开项目的App_Data 文件夹,发现数据库文件已经存在
6.前台Login页面如下:
<body> <div id="loginState"> @ViewBag.LoginState </div> @*<form action="~/Account/Login" method="post" class="form-horizontal">*@ @using (Html.BeginForm("Login", "Account", FormMethod.Post)) { <form class="form-horizontal"> <div class="form-group"> <label for="email" class="col-sm-2 control-label">Email</label> <div class="col-sm-10"> <input type="email" class="form-control" id="email" name="email" placeholder="Email"> </div> </div> <div class="form-group"> <label for="pwd" class="col-sm-2 control-label">Password</label> <div class="col-sm-10"> <input type="password" class="form-control" id="pwd" name="pwd" placeholder="Password"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <label> <input type="checkbox"> Remember me </label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Sign in</button> </div> </div> </form> } </body>
form的action和method的位置可以写成固定的,这样的话部署发生变化时有可能地址会不可用(如放在IIS根目录下和虚拟目录下是不同的)
<form action="~/Account/Login" method="post" class="form-horizontal">
我们对Login.cshtml中的form做一点改良,使用HtmlHelper动态计算路由地址就是其中的一种方法。
添加下面一句代码,将form中内容放到 {} 中去即可
@using (Html.BeginForm("login", "Account", FormMethod.Post)) { }
运行,到浏览器中查看源代码,可以看到生成的源代码和原来一样。
权责申明
作者:编程小纸条 出处: https://www.cnblogs.com/miro/category/620362.html