webapi框架搭建-数据访问ef code first
webapi框架搭建系列博客
为什么用ef?
我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推翻别人的技术路线,甚至屡屡推翻自己从前的想法,这种专研的精神固然不错,但随着年龄的增大,会发现这种习惯已将自己弄得很累,其实真没有必要。我觉得技术上永远没有“完美”的解决方案,如果揪着缺点去比较和选择,不管最终选择了什么,以后都会后悔。因为你总是看到它身上的缺点。orm的框架也有很多,大家都说entityframework性能有点差,它生成的sql语句简直看不下去,我也考虑过dapper,但它要写一些sql语句,这不是我想干的事(纯属个人代码风格偏好)。分享下为什么会选择ef。我的orm框架的要求是这样的:1、简单快速上手(因为我不太想将太多的精力花在这上面,而ef各方法的资料还是很全的);2、性能只要过得去就行(又不是要开发对性能要求很苛刻的产品,我想微软的产品也不至于性能很低吧);3、后面如果要换数据库,最好不要改代码(这点是我喜欢ef的主要原因,只要将数据连接换一下,dll包换一下,数据库就可以从mysql,sqlserver,oracle任意切换)
为什么用code first?
1、代码简洁
相比db first,code first的代码更简洁,基本所有的代码都是真正需要的。而db first有很多自动生成的辅助型的代码。
2、编写简单
db first在sqlserver下也简单,但如果是mysql或是oracle,在vs的配置上就会遇到很多问题,我初学时经常遇到vs连mysql失败,或是连接成功后总是生成代码时失败。而用code first,你只要写实体的代码就行,虽然是”多写了一些代码“,但其实速度上比db first还是快的。
参考资料
推荐:http://www.entityframeworktutorial.net/code-first/entity-framework-code-first.aspx
微软官方:https://msdn.microsoft.com/en-us/library/aa937723(v=vs.113).aspx
下面介绍如何实用
用法和步骤
引用相关包
如果是sqlserver数据库,只要引用entityframework就行
如果是mysql数据库,引用mysql.data.entity包(依赖mysql.data包)
注意:在实际开发中发现有些版本的mysql.data.entity包是有bug的,如版本6.10.5,建议安装6.9.10(测试没有问题)
如果是oracle数据库,引用oracle.managedDataAccess.EntityFramework包(依赖Oracle.ManagedDataAccess包)
oracle注意:微软也出过oracle的连接库,但不支持ef,现在都提倡用oracle出的odp.net技术。而odp.net以前的版本是基于oracle.dataaccess.dll的,这个有x86和x64位之分,开发时很不方便,建议用oracle.managedDataAccess.dll.
引用包后,会在web.config里自动生成如下相应的配置信息:
1)在configuration--》configSections节点下面生成名为entityFramework的section
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
2)
编写实体类
如下代码
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace webapi.Entities { [Table("Test")] public class TestTable { [Key,Column(TypeName = "varchar"),MaxLength(50)] public string Id { set; get; } [Column(TypeName = "int")] public int? Age { set; get; } [Column(TypeName = "datetime")] public DateTime? CreateDateTime { get; set; } } }
实体通过dataannotations的方式描述在数据库里类型,长度、主键等。没有必要死记他们的写法,不会时参考http://www.entityframeworktutorial.net/code-first/stringlength-dataannotations-attribute-in-code-first.aspx就行。
编写数据库上下文
1)创建继承自DbContext的类
using System.Data.Entity; namespace webapi.Entities { public class DB : DbContext { /// <summary> /// name=DBConnection,DBConnection为数据库连接的名字,即web.config配置文件节点connectionStrings,name值为DBConnection的数据库连接字符串 /// </summary> public DB() : base("name=DBConnection") { } #region 配置所有的数据库表 public DbSet<TestTable> TestTables { set; get; } #endregion } }
2)在web.config里配置数据库连接字符串
由于数据库连接字符串比较重要,为方便,在实际的开发中经常单独配置在一个文件里,然后在web.config里去引用配置文件。
创建ConnectionStrings.config文件,内容如下
<?xml version="1.0" encoding="utf-8"?> <connectionStrings> <!--sqlserver连接字符串--> <!--<add name="DBConnection" providerName="System.Data.SqlClient" connectionString="Data Source = localhost; Initial Catalog = WebApiFramework; User Id = sa; Password = 1" />--> <!--mysql连接字符串--> <add name="DBConnection" providerName="MySql.Data.MySqlClient" connectionString="Data Source=localhost;Initial Catalog=webapi;User id=root;Password=root;charset=utf8;port=3306;" /> <!--oracle连接字符串--> <!--<add name="DBConnection" providerName="Oracle.ManagedDataAccess.Client" connectionString="Data Source=localhost;User Id=system;Password=root;" />--> </connectionStrings>
上面将常用的数据库的连接方式举例出来,以后要换数据库,只要按一下数据库的连接字符串就行。
编写测试接口
code first的基本来法就上面三步了。我们来测试下是否正常。
1)创建测试接口,代码如下:
using System.Linq; using System.Web.Http; using webapi.Entities; namespace webapi.example { public class EFTestController : ApiController { public IHttpActionResult Get() { using (DB db=new DB()) { var list = db.TestTables; return Ok(list.ToList()); } } } }
2)运行接口
用postman访问接口地址(get方法):http://localhost:101/api/EFTest,返回结果为空json数组:[];
注意:我的电脑上已经安装了mysql,但并没有webapi数据库啊,为什么接口正常运行呢?原因是entityframework会自动检测是否存在数据库,如果没有就会去创建。下面是ef自动创建的数据库
注意到数库里还自动生了了”_MigrationHistory"的表,它有什么用处呢?先说下几个例子
(1)如果现在在TestTable实体里增加一个数据库对应的字段,或是在数据库表里增加一个字段,编译后再访问上面同样的接口,会出现下面的错误
{
"Message": "The model backing the 'DB' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269)."
}
(2)如果在(1)的操作后,删除了_MigrationHistory表再访问接口,同样会报错,但错误的内容已经变成如下
Unknown column 'Extent1.AddColumn' in 'field list'
(3)如果在(2)的操作后,删除TestTable实体在(1)中增加的字段,再次访问接口时会运行成功
_MigrationHistory作用总结:用于比较model和数据库的版本是否一致,如果不一致则报(1)中的错误,不会执行sql语句;如果这个表没有,就不会进行model和数据库的版本检测,直接执行ef生成的sql语句。code first在目标数据库没有的情况下会创建数据库,并创建_MigrationHistory表,也可以用于连接已经存在数据库(此时目标数据库里可能没有_MigrationHistory),但这时要确保自己的实体和目标数据库的表是一样的。
增加Migration机制
Migration即是将model的更改应用到数据库里,分自动和手动两种机制,具体可参考http://www.entityframeworktutorial.net/code-first/migration-in-code-first.aspx。
1)在vs的程序包管理器控制台里运行enable-migrations –EnableAutomaticMigration:$true
如果是mysql数据库,可能会出现如下错误:
Checking if the context targets an existing database...
No MigrationSqlGenerator found for provider 'MySql.Data.MySqlClient'. Use the SetSqlGenerator method in the target migrations configuration class to register additional SQL generators.
原因:codefirst默认是生成sqlserverr的sql语句,要在migrations的配置代码里改成用mysql 。此时在项目里已经成功生成了Migrations文件夹,里面有个Configurations.cs类,修改Configurations.cs代码,如下
using System.Configuration; namespace webapi.Migrations { using System.Data.Entity.Migrations; internal sealed class Configuration : DbMigrationsConfiguration<webapi.Entities.DB> { public Configuration() { AutomaticMigrationsEnabled = true;//自动更新数据库 AutomaticMigrationDataLossAllowed = true;//重命名和删除表字段时会丢失数据,设置成允许,否则此情况下同步数据库会出错 var providerName = ConfigurationManager.ConnectionStrings["DBConnection"].ProviderName; if (providerName == "MySql.Data.MySqlClient") { SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());//如果数据库用mysql,加上这一句 } } protected override void Seed(webapi.Entities.DB context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. } } }
如果AutomaticMigrationDataLossAllowed不设置成true(默认为false),重命名实体的属性名是会出现如下错误
Automatic migration was not applied because it would result in data loss. Set AutomaticMigrationDataLossAllowed to 'true' on your DbMigrationsConfiguration to allow application of automatic migrations even if they might cause data loss. Alternately, use Update-Database with the '-Force' option, or scaffold an explicit migration.
2)修改数据库上下文的配置
using System.Data.Entity; namespace webapi.Entities { public class DB : DbContext { /// <summary> /// name=DBConnection,DBConnection为数据库连接的名字,即web.config配置文件节点connectionStrings,name值为DBConnection的数据库连接字符串 /// </summary> public DB() : base("name=DBConnection") { // 默认策略为CreateDatabaseIfNotExists,即如果数据库不存在则创建,用migrations时改成MigrateDatabaseToLatestVersion,即每次第一次访问数据库时同步最新的数据库结构 Database.SetInitializer(new MigrateDatabaseToLatestVersion<DB, webapi.Migrations.Configuration>("DBConnection")); } #region 配置所有的数据库表 public DbSet<TestTable> TestTables { set; get; } #endregion } }
加入migrations机制后,如果在实体里改变了实体的结构,在第一次网站访问时就会自动去更新数据库的结构,非常方便。
经验:在开发时要避免手动去改数据库的结构,这样会导致各种migrations失败的错误,code first,顾名思义就是让我们以code为先,我们通常修改code的实体间接的去同步数据库结构。手动改数据库那是db first的思想。在项目正式上线还是要慎用migration机制,最好是将AutomaticMigrationDataLossAllowed还原为默认false,不然一个实体的重命名会导致数据库表的数据丢失,切记切记。