ASP.NET MVC企业级实战
上QQ阅读APP看书,第一时间看更新

2.6 Code First开发方式

本节介绍通过Code First开发建立新数据库。借助Code First可以选择使用类的特性和属性执行配置,或者使用XML配置文件来配置,当然也可以使用FluentAPI执行配置。

Code First使用场景:对于已经存在了模型类型的项目,怎么使用EF呢?Code first,也叫POCO+Code Only。

Code only,顾名思义,只需要代码,不需要Edmx模型。EF提供了通过类型的结构推断生成SQL并创建数据库中的表,而且能够通过类型的成员推断出实体间的关系,开发人员只需要编写实体类就可以进行EF数据库的开发。

优势:

● 使开发更进一步简洁化。

● 开发效率又一次提高。

● 自动化程度进一步提高。

● 可以适用于原有的老项目。

劣势:

● 性能不怎么好。

● 了解的人比较少。

● 学习成本相对较高,对开发人员的要求相对较高。

Code First有两种配置数据库映射的方式,一种是使用数据属性DataAnnotation,另外一种是使用Fluent API。DataAnnotation的配置方式需要给定义实体和值对象的类和类中的属性加上与数据库映射相关的配置标签。而Code First Fluent API是在DbContext中定义数据库配置的一种方式。要使用Fluent API就必须在自定义的继承自DbContext类中重载OnModelCreating方法。这个方法的签名如下:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

通过modelBuilder这个对象的Entity<>泛型方法来配置DbContext中每个类的数据库映射。我们可以通过FluentAPI配置数据表的名字:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>().ToTable(“CustomerInfo”)
}

这里只简单讲解一下DataAnnotation,关于Code First Fluent API的使用,大家可以自己查找相关资料学习。

2.6.1创建Code First Demo

创建Code First的操作步骤如下:

(1)创建应用程序

打开Visual Studio,选择“文件→新建→项目→Visual C#→Web→ASP.NET MVC 4 Web应用程序”,如图2-35所示。

图2-35

单击“确认”按钮后,项目模板选择“空”,然后再单击“确认”按钮。

(2)引入程序集EntityFramework和System.Data.Entity

我们可以使用NuGet来进行安装,右击选择“引用→管理NuGet程序包”,再选择“联机”选项卡,选择“EntityFramework”程序包,单击“安装”按钮。

当然,如果觉得以上步骤比较麻烦,也可以使用一种简单的方式来引用:选中项目MvcFirstCode,右击,选择“添加→新建项”,如图2-36所示。

图2-36

单击“添加”按钮,弹出如图2-37所示的界面。

图2-37

然后删除Model1.edmx,可以在项目中看到这两个引用已经自动添加上,如图2-38所示。

图2-38

(3)创建模型

在Models文件夹下面新建Order.cs和OrderDetail.cs模型类文件,Order代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MvcFirstCode.Models
{
    public class Order
    {
      /// <summary>
      /// 如果属性名后面包含Id,则默认会当成主键,可以不用添加[Key]属性
      /// </summary>
      [Key]
      public int OrderId { get; set; }
      /// <summary>
      /// 订单号
      /// </summary>
      [StringLength(50)]
      public string OrderCode { get; set; }
      /// <summary>
      /// 订单金额
      /// </summary>
      public decimal OrderAmount { get; set; }
      /// <summary>
      /// 导航属性设置成virtual,可以实现延迟加载
      /// </summary>
      public virtual List<OrderDetail> OrderDetail { get; set; }
    }
}

OrderDetail代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcFirstCode.Models
{
    public class OrderDetail
    {
      [Key]
      public int OrderDetailId { get; set; }
      /// <summary>
      /// 订单明细单价
      /// </summary>
      public decimal Price { get; set; }
      /// <summary>
      /// 订单明细数量
      /// </summary>
      public int Count { get; set; }
      /// <summary>
      /// 外键,如果属性名和Order主键名称一样,默认会当成外键,可以不加ForeignKey特性。注
      /// 意ForeignKey里面的值要和导航属性的名称一致
      /// </summary>
      [ForeignKey("Order")]
      public int OrderId { get; set; }
      /// <summary>
      /// 导航属性
      /// </summary>
      public virtual Order Order { get; set; }
  }
}

EF支持的完整注释列表如下:

● KeyAttribute

● StringLengthAttribute

● MaxLengthAttribute

● ConcurrencyCheckAttribute

● RequiredAttribute

● TimestampAttribute

● ComplexTypeAttribute

● ColumnAttribute

● TableAttribute

● InversePropertyAttribute

● ForeignKeyAttribute

● DatabaseGeneratedAttribute

● NotMappedAttribute

(4)在配置文件中写连接字符串

在Web.config中添加如下配置节点,注意providerName属性必填,否则会报错。

  <connectionStrings>
    <add name="MvcFirstCodeContext" connectionString="server=.\MSSQLSERVER2012;database=MvcFirstCode; uid=sa; pwd=yujie1127"
providerName="System.Data.SqlClient"/>
  </connectionStrings>

(5)创建上下文类MvcFirstCodeContext.cs

继承自DbContext,需要引入命名空间“using System.Data.Entity; ”(引入命名空间的快捷方法是:将鼠标移动到DbContext上,然后按Ctrl+Alt+F10组合键,再按回车键即可)。调用父类构造方法,传递连接字符串"name=xx",这个xx就是刚才配置文件里面配置的连接字符串name的名称。代码如下:

using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace MvcFirstCode.Models
{
    public class MvcFirstCodeContext:DbContext
    {
      /// <summary>
      /// 注意这里的name要和配置文件里面配置的上下文连接字符串名称一致
      /// </summary>
      public MvcFirstCodeContext() : base("name=MvcFirstCodeContext") { }
      public DbSet<Order> Order { get; set; }
      public DbSet<OrderDetail> OrderDetail { get; set; }
    }
}

(6)根据类型创建数据库表

先生成项目MvcFirstCode,然后在Controllers目录下面添加控制器Home,如图2-39所示。

图2-39

在Index Action中添加如下代码:

//当操作的表存在时不进行创建,如果不存在就创建
db.Database.CreateIfNotExists();

注意,在这里我们使用context.Database.CreateIfNotExists()完成数据库中表的创建,调用context.SaveChanges()方法完成数据保存。

再次生成项目MvcFirstCode,按Ctrl+F5组合键运行项目,如图2-40所示。

图2-40

出现图2-40显示的内容说明代码已经执行成功了。

打开视图→服务器资源管理器,如图2-41所示。

图2-41

从图2-41中可以看到数据库和表也已经创建好了。下面新建一条订单数据,由于我们之前新建MVC项目的时候选择的是空项目,因此刚才新建Home控制器生成的Create.cshtm和Edit.cshtml视图要注释掉代码:

@*   @Scripts.Render("~/bundles/jqueryval")*@

因为是空项目,所以默认并没有启用文件合并和压缩功能,运行结果如图2-42、图2-43所示。

图2-42

图2-43

2.6.2 关于EF实例的创建问题

在Home控制器中,直接在类HomeControll中新建了一个EF数据库对象,代码如下:

private MvcFirstCodeContext db = new MvcFirstCodeContext();

什么时候释放这个db对象呢?既不能每次调用都直接放到using()方法中新建一个db对象,也不能对db对象使用单例模式。因为每次调用都在using()方法中新建一个db对象会频繁地连接关闭数据库,而且更要命的是在并发操作的情况下会产生脏数据。对db使用单例模式的话,有多个用户操作数据库的时候操作的就是同一个db对象了,这样也是不行的。

我们可以考虑把这个EF对象存到一个线程中,新建一个Base控制器,作为其他控制器的基类,然后我让Home控制器继承自Base控制器,Base控制器中,添加命名空间引用:

using System.Runtime.Remoting.Messaging;

然后添加如下属性:

public MvcFirstCodeContext db
{
        get
        { //从当前线程中获取MvcFirstCodeContext对象
              MvcFirstCodeContext db = CallContext.GetData("DB") as MvcFirstCodeContext;
              if (db == null)
              {
                db = new MvcFirstCodeContext();
                CallContext.SetData("DB", db);
              }
              return db;
        }
}

这时就可以在Home控制器中注释掉如下代码:

//private MvcFirstCodeContext db = new MvcFirstCodeContext();