MVC+EF6教程一:使用EF初始化数据库
第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
浙公网安备 33010602011771号