C# .NET MVC 常用框架 —— EF框架(Code First)环境搭建
框架了解
ORM框架
ORM框架是对于使用了ORM(Object Relational Mapping:对象关系映射)技术的框架的一种统称。其用来把对象模型表示的对象关系映射到基于SQL的关系模型数据库结构中去。如此,在操作实体对象的同时,就不需要复杂的SQL语句,直接可以以操作实体对象的属性和方法。ORM技术其实就是在对象和关系之间提供了一个桥梁,用来对前台的对象型数据和数据库中的关系型数据进行转换。
EF框架
EF(Entity Framework)框架是微软在 .NET Framework SP1 中提供的一种ORM框架。
EF框架主要分三种模式:
DataBase First:数据库优先。先设计数据库,然后映射成对象和上下文。
Model First:模型优先。先创建实体模型,然后生成数据库和创建对象与上下文。
Code First:代码优先。手动创建实体模型、数据库上下文以及映射关系,然后生成数据库。
EF框架Code First模式创建
第一步:EF环境搭建
★ 因为微软自己的OracleClient性能已经落后了,不建议使用。所以如果需要连接Oracle数据库,建议再搜索安装“Oracle.ManagedDataAccess”类库,步骤同上。
第二步:配置连接字符串(这里演示 SQL Server 和 Oracle 两种连接方式)
SQL Server 连接字符串与 Oracle 连接字符串分别如下(数据库名、用户名、密码根据情况自行修改):
<!--配置数据库链接-->
<connectionStrings>
<!--SQL Server 连接字符串-->
<add name="SQLServerConnStr" connectionString="Data Source=.; Initial Catalog=db_KoolTube; User ID=sa; Password=951026" providerName="System.Data.SqlClient" />
<!--Oracle 连接字符串-->
<add name="OracleDbContext" connectionString="User Id=scott;Password=test;Data Source=(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = test_orcl)
)
)" providerName="Oracle.ManagedDataAccess.Client"/>
</connectionStrings>
★ 连接字符串根据自己使用的数据库选择其一即可,这里演示两种数据库,所以两种都先保留。
★ 切记数据库用户需要拥有创建表等基本权限。
第三步:创建实体类,即Model。
这里创建两个实体类,方便一会儿演示一下外键的使用
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace KoolTube.Web.Models {
/// <summary>
/// 视频分类 实体
/// </summary>
public class Catalog {
/// <summary>
/// ID
/// </summary>
[Key]
[StringLength(32)]
public string PKID {
get;
set;
}
/// <summary>
/// 分类名称
/// </summary>
[StringLength(50)]
[Required]
public string NAME {
get;
set;
}
/// <summary>
/// 添加时间
/// </summary>
public DateTime? ADDTIME {
get;
set;
}
/// <summary>
/// 类型
/// </summary>
public string STYLE {
get;
set;
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace KoolTube.Web.Models {
public class Video {
/// <summary>
/// ID
/// </summary>
[Key]
[StringLength(32)]
public string PKID {
get;
set;
}
/// <summary>
/// 标题
/// </summary>
[StringLength(50)]
public string TITLE {
get;
set;
}
/// <summary>
/// 内容
/// </summary>
public string CONTENT {
get;
set;
}
/// <summary>
/// 与类型 Catalog.cs 绑定
/// </summary>
public string CATALOGID {
get;
set;
}
/// <summary>
/// 绑定 Catalog 实体 外键
/// </summary>
[ForeignKey("CATALOGID")]
public Catalog Catalog {
set;
get;
}
}
}
★ 关于属性标记,需要注意几点:
1、[Key]:标记主键。
实体类中有[Key]标记的属性,会自动被EF框架识别为主键。
如果没有[Key]标记,EF会自动识别名称为“ID”或者"实体名ID"的属性作为主键,主键必须声明成属性且必须是public权限。
如果实体类中没有[Key]标记,且没有名称为“ID”和“实体名ID”的属性,在自动映射数据库时,会报错如下:
Exception : EntityType has no key defined. Define the key for this EntityType.
2、[ForeignKey("CATALOGID")]:标记外键。
通常指定外键,我们可以定义一个属性(切记类型一致),使用ForeignKey手动指派。如上例。
另外,如果没有定义指定的属性,只定义了导航对象,则会自动生成名称为“对象名_主键名”的外键。如下:
public class Video {
/// <summary>
/// ID
/// </summary>
[Key]
[StringLength(32)]
public string ID {
get;
set;
}
/// <summary>
/// 自动生成名为 Catalog_PKID 的外键(注意:这里的PKID指的是Catalog实体的主键)
/// </summary>
public Catalog Catalog {
set;
get;
}
}
还有一种情况,即如果创建导航对象时存在一个与要对应的实体类的主键相同名称的属性,那么这个属性会被默认当做外键。如下:
public class Video {
/// <summary>
/// ID
/// </summary>
[Key]
[StringLength(32)]
public string ID {
get;
set;
}
public string PKID {
get;
set;
}
/// <summary>
/// 创建导航对象时,有一个属性 PKID 与 Catalog 实体类的主键 PKID 同名,那么这个属性会默认被当做外键
/// </summary>
public Catalog Catalog {
set;
get;
}
}
3、[Required]:必需标记。即不能为空。
这个字段在映射数据库中的体现仅仅是该字段是否可以为空。
我们这里需要注意的不是数据库,而是后台代码中经常会使用到的 ModelState.IsValid 判断。
ModelState.IsValid 是用来判断表单是否满足实体类中所标记的限定。如上,我们在Catalog实体类中的Name属性标记了[StringLength(50)] 和 [Required] 两个限制,所以,在前台页面提交表单到后台时,如果Name属性的长度超过了50或者Name为空,ModelState.IsValid 的值就会为False。
需要重点注意的是:它判断的是提交表单时的状态,在后台语句中的修改时不对其起作用的,这也是很多人的Model中字段明明不为空,但ModelState.IsValid却一直等于false,报错某字段为空的原因。
如下面三个示例:
第四步:添加上下文,映射数据表(SQL Server数据库)
由于映射数据表一般情况下载程序运行时就应该进行,所以,我们在 App_Start 文件夹下创建类 DataContext.cs 继承 EF 框架的 DbContext 类(如果EF框架没有安装或安装失败,这里会报错)
public class DataContext : DbContext{
//构造函数指定链接数据库的字符串
public DataContext() : base("name = SQLServerConnStr") {
}
//添加dbset 映射数据表
public DbSet<Catalog> catalog {
set;
get;
}
public DbSet<Video> video {
set;
get;
}
}
同时,我们需要在全局类 Global.asax 中初始化数据库。
初始化数据库的语句建议写在单独的类中,然后在全局类中调用。因为这里只是测试,为了方便,所以直接写在 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);
//初始化数据库
using (var dbContext = new DataContext()) {
//如果不存在就创建 如果存在就不执行
dbContext.Database.CreateIfNotExists();
}
}
}
第五步:运行代码,映射数据库和数据表
★ 注意:这里,SQL Server 数据库尤其要注意一点。在运行代码之前,是不能存在与映射数据库同名的数据库的,否则,在上述初始化数据库时,会判断数据库已存在,不执行数据表的映射。切记,数据库也是由代码自动生成,切勿手动创建数据库。
SQL Server 数据库创建成功。
第四步:我们返回第四步,映射Oracle数据库与表。
public class DataContext : DbContext{
//构造函数指定链接数据库的字符串
public DataContext() : base("name = OracleDbContext") {
}
//添加dbset 映射数据表
public DbSet<Catalog> catalog {
set;
get;
}
public DbSet<Video> video {
set;
get;
}
}
因为我们之前配置连接字符串的时候配置了两个,所以这里只需要更换一下连接字符串的名称即可。
★ 但是,因为我们之前已经连接了SQL Server数据库,所以,这里会报一个错误。(如果直接连接Oracle数据库,不会遇到这个报错)
Oracle.ManagedDataAccess.Client.OracleException:“ORA-01918: 用户 'dbo' 不存在”
很明显 :dbo用户是SQL Server 中的用户,而我们现在连接的是Oracle数据库,却报了这个异常。这是因为,之前连接SQL Server 之后,我们直接更换了Oracle的驱动,导致EF框架的脚本文件没有更新过来。我们可以重新生成一下CodeFirst 的脚本文件。当然,这里我们采用另一种更简单的方法,直接在上下文中为Oracle指定连接用户。如下:
public class DataContext : DbContext{
public DataContext() : base("name = OracleDbContext") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//指定Oracle连接用户
modelBuilder.HasDefaultSchema("SCOTT");
}
//添加dbset 映射数据表
public DbSet<Admin> admin {
set;
get;
}
public DbSet<Catalog> catalog {
set;
get;
}
public DbSet<Video> video {
set;
get;
}
}
★ 记得这里指定的用户名为大写,且该用户要在Oracle实例中存在并且要拥有创建表等基本权限。
第五步:运行代码
最后,再补充两点。
1、映射生成的Oracle数据表默认命名是复数的,也就是实体Model名+s。
如果不需要复数,可以通过在“上下文”中加入以下代码。(与上述指定登录用户在同一个方法中)
protected override void OnModelCreating(DbModelBuilder modelBuilder){
//自动建表取消复数
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
2、映射生成的Orale数据表中字段默认搜是带有双引号的,且区分大小写的。有些情况下不带双引号,不区分大小写不会报错,但是如果报错“找不到表或者找不到字段”,就需要检查是否加上双引号或者大小写是否正确。如下:
Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.1.0
Connected as scott
SQL> SELECT * FROM catalogs;
SELECT * FROM catalogs
ORA-00942: 表或视图不存在
SQL> SELECT * FROM Catalogs;
SELECT * FROM Catalogs
ORA-00942: 表或视图不存在
SQL> SELECT * FROM "Catalogs";
PKID NAME ADDTIME STYLE
----------------------------------------------------------------- -------------------------------------------------------------------------------- ----------- --------------------------------------------------------------------------------
9694ba37c9e94bdd88364d333d8a6555 测试 2018/7/30 2 1
867fa72aa3744005bae718b861829003 .NET MVC5 2018/7/30 2 1
d0f964d469cd49d1afbf9b3b66c3eedb 小视频 2018/7/31 2 1
SQL> SELECT T.name from "Catalogs" T;
NAME
--------------------------------------------------------------------------------
测试
.NET MVC5
小视频
SQL> SELECT T."NAME" FROM "Catalogs" T;
NAME
--------------------------------------------------------------------------------
测试
.NET MVC5
小视频
SQL> SELECT T."name" FROM "catalogs" T;
SELECT T."name" FROM "catalogs" T
ORA-00942: 表或视图不存在
SQL> SELECT T."name" FROM "Catalogs" T;
SELECT T."name" FROM "Catalogs" T
ORA-00904: "T"."name": 标识符无效