公司的项目中用的 ORM 是 Dapper,代码中充斥着大量的 SQL 语句,为了少写 SQL 语句,领导让我把 EF6 也加进去看会不会有问题。按照指示,我在新的代码分支引入了 EF6 并做了 CRUD 的测试,结论是混合使用 Dapper 和 EF6 没问题。为了让团队中没用过 EF 的同事也能快速上手 EF,我把我的试用记录重新整理了一下,于是乎就有了本文。
1、如何通过 EF6 来连接 MySQL?
1、安装 MySQL 的 .NET 驱动
要在 .NET 项目中连接 MySQL 首先得安装 MySQL 的 .NET 驱动。这个驱动是向下兼容的,官方下载地址:MySQL Connector/NET。
2、安装 MySql.Data.EntityFramework
Install-Package MySql.Data.EntityFramework -Version 8.0.15
上面的 NuGet 命令会自动帮你把 EF6 和 MySql.Data 都安装好,无需额外再安装。
3、创建模型类
有了和数据库中表对应的模型类,才能方便的操作数据库而不必写 SQL 语句。如定义一个 Person 实体,示例如下:
[Table("person")] // 这里不仅可以自定义表的 Name 还可以自定义表的 Schema
public class Person {
[Key]
public Int32 ID { get; set; }
public String Name { get; set; }
public DateTime Birthday { get; set; }
public Int32 NationID { get; set; }
public Nation Nation { get; set; }
}
定义实体的注意事项:
- 1、模型类名与表名不必相同。如果不同,则需要用 TableAttribute 标注一下;如果相同,则可以省略该 Attribute。
- 2、主键名不必非得是 ID。如果不是,则需要用 KeyAttribute 标注一下;如果是 ID,则可以省略该 Attribute。EF 遵循“约定大于配置”的开发原则,比如 EF 中主键名默认为 ID 就是 EF 的一个内置约定,EF 还支持自定义约定。
4、创建数据库上下文类
有了数据库上下文,就可以连接数据库了,然后在上下文中定义相应的 DbSet(实体对象集合),就能直接对数据库进行 CRUD 操作了。如创建一个 Demo 的上下文,示例如下:
public class DemoDbContext : DbContext {
// 声明 DbSet,实现 CRUD 的方法定义在 DbSet 中
public DbSet<Person> Persons { get; set; }
public DbSet<Nation> Nations { get; set; }
public DemoDbContext() : base("name=ConnectionString") {
// 关闭迁移,EF Code First 默认会在 Model 发生改变后自动更新数据库
Database.SetInitializer<DemoDbContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
// 解决表名变复数的问题,EF 生成 SQL 语句时默认会将实体名变成复数
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
定义上下文的注意事项:
- 1、创建的数据库上下文类必须继承 DbContext 类。
- 2、在上下文类的构造函数中通过 base 的方式指定数据库连接字符串。base 的参数写法有多种,常见的写法如下:
base("ConnectionString")
base("name=ConnectionString")
base(new MySqlConnection("..."), false)
- 3、由于 EF 的迁移功能过于复杂,且非必要,一般不用,在构造函数中关闭即可。
- 4、EF 默认生成的表名是 Model 名的复数,可在 OnModelCreating 中移除该转换规则。
2、如何通过 EF6 来实现 CRUD?
2.1、Create 添加
- 1、向一个表中添加一条数据,示例如下:
using (var context = new DemoDbContext()) {
var p = new Person() { Name = "Andy", Gender = 1 };
context.Persons.Add(p);
context.SaveChanges(); // 返回受影响行数 1
}
上面的代码会生成 1 条 INSERT 语句和 1 条 SELECT 语句。
- 2、同时向存在主外键的两个表中添加一条数据,示例如下:
using (var context = new DemoDbContext()) {
var n = new Nation() { Name = "China" };
var p = new Person() { Name = "Mark", Gender = 1, NationID = n.ID };
context.Nations.Add(n);
context.Persons.Add(p);
context.SaveChanges(); // 返回受影响行数 2
}
上面的代码会生成 1 条 INSERT 语句和 2 条 SELECT 语句。
- 3、一次添加多个并附加事务:
String connectionString = "server=localhost;port=3306;database=demo;uid=root;pwd=";
using (MySqlConnection connection = new MySqlConnection(connectionString)) {
connection.Open();
MySqlTransaction transaction = connection.BeginTransaction();
try {
using(var context = new DemoDbContext(connection)) {
context.Database.UseTransaction(transaction);
List<Person> ps = new List<Person>();
ps.Add(new Person { Name = "Mark", Gender = 1 });
ps.Add(new Person { Name = "Jack", Gender = 1 });
ps.Add(new Person { Name = "Tom", Gender = 1 });
context.Persons.AddRange(ps);
context.SaveChanges();
}
transaction.Commit();
} catch {
transaction.Rollback();
throw;
}
}
2.2、Retrieve 查询
- 1、EF 查询支持 LINQ 写法,必须在最后调用
ToList()
才会执行查询,示例如下:
using (var context = new DemoDbContext()) {
context.Database.Log = Console.WriteLine;
var list1 = (from p in context.Persons where p.ID == 1 select p).ToList();
var list2 = (from p in context.Persons select p.Name).ToList();
var query = from p in context.Persons select p;
query = from p in query where p.ID >= 1 select p;
query = from p in query where p.NationID == 1 select p;
query = from p in query orderby p.Name descending select p;
query.ToList();
}
- 2、EF 查询支持 Lambda 写法,示例如下:
using (var context = new DemoDbContext()) {
context.Database.Log = Console.WriteLine;
// LIMIT 1
var p1 = context.Persons.FirstOrDefault();
// LIMIT 2,不会做参数化处理
var p2 = context.Persons.Single(p => p.ID == 5);
// LIMIT 2,会自动做参数化处理
var p3 = context.Persons.Find(3);
// 会自动做参数化处理
var p4 = context.Persons.Where(p => p.Name.Contains("Andy")).ToList();
// 只查询部分数据行,可用这个实现分页查询
var p5 = context.Persons.OrderBy(p => p.Name).Skip(3).Take(5).ToList();
// 带条件的分页查询
var p6 = context.Persons.Where(p => p.ID > 0).OrderBy(p => p.Name).Skip(3).Take(5).ToList();
}
- 3、查询关联数据,示例如下:
using (var context = new DemoDbContext()) {
var persons = context.Persons.Include(p => p.Nation).ToList();
}
上面的代码会生成 1 条内连接 SELECT 语句。
2.3、Update 修改
- 1、修改一条确定存在的数据时,用如下语句:
using (var context = new DemoDbContext()) {
var p = new Person() { ID = 3, Name = "Andy" };
context.Persons.Attach(p);
context.Entry(p).Property(i => i.Name).IsModified = true;
context.SaveChanges(); // 返回受影响行数
}
上面的代码会生成 1 条 UPDATE 语句,数据不存在时会报错。
- 2、如果需要确认数据存在后再修改的话,用如下语句:
using (var context = new DemoDbContext()) {
var p = context.Persons.Find(1); // 也可以用 FirstOrDefault 或其它查询方法
if (p != null) {
p.Name = "Peter";
context.Persons.Attach(p);
context.Entry(p).Property(i => i.Name).IsModified = true; // 指定更新字段
context.SaveChanges(); // 返回受影响行数
}
}
上面的代码会生成 1 条 UPDATE 语句和 1 条 SELECT 语句。
2.4、Delete 删除
- 1、删除一条确定存在的数据时,用如下语句:
using (var context = new DemoDbContext()) {
var p = new Person() { ID = 1 };
context.Persons.Attach(p);
context.Persons.Remove(p);
context.SaveChanges(); // 返回受影响行数
}
上面的代码会生成 1 条 DELETE 语句,数据不存在时会报错。
- 2、如果需要确认数据存在后再删除的话,用如下语句:
using (var context = new DemoDbContext()) {
var p = context.Persons.FirstOrDefault(it => it.ID == 1);
if (p != null) {
context.Persons.Attach(p);
context.Persons.Remove(p);
context.SaveChanges();
}
}
3、如何更好的运用 EF6 来完成工作?
技术好的人经常讲业务场景,相反,有些技术差的人却喜欢不由分说的吐槽那些他根本就没搞懂的技术。在 .NET 圈子里,有人对 EF 是爱不释手,也有人对 EF 是各种吐槽。
我很喜欢的一句话是:“没有不好的技术,只有没被用好的技术”,我的理解是任何技术都有局限性,作为程序员,我们要做的是结合实际业务场景来选用最合适的技术。要想在项目中更好的运用 EF,就得更多的了解 EF 技术,本节就来分享一下我试用 EF6 过程中的一些收获。
3.1、传说中 EF 的三种模式
为什么说 EF 的三种模式是传说呢?因为新版的 EF 默认只支持 Code First 这一种模式了。要想用 Database First 或 Model First 还得把 Visual Studio 降级到 VS10 或 VS12 才行,实在没必要,下面简单罗列下每种模式的特点:
- 1、Database First:即数据库优先,先创建好数据库和表,然后自动生成 EDM(实体数据模型)文件,再由 EDM 文件生成模型类。当现有数据库结构比较成熟稳定时,可用这种模式实现快速开发。
- 2、Model First:即模型优先,先创建可视化的 EDM 文件,然后由 EDM 文件来自动生成模型类和数据库。开发速度快,但代码冗余。写个小 Demo 还行,但企业级开发一般没人用这个模式。
- 3、Code First:即代码优先,先写好模型类,然后自动生成数据库,没有 EDM 文件。代码简洁可控,也是官方和业界首推的模式。
3.2、EF6 执行原生 SQL 查询
总会有些时候,我们为了性能或者其它各种各样的缘故,而不得不写 SQL 语句,EF 提供了直接执行 SQL 语句的方法SqlQuery()
。
- 1、执行无参数的原生 SQL 查询,示例如下:
using (var context = new DemoDbContext()) {
var persons = context.Persons.SqlQuery("SELECT * FROM Person").ToList();
}
- 2、执行带参数的原生 SQL 查询,示例如下:
using (var context = new DemoDbContext()) {
var sql = "SELECT t.* FROM Person t WHERE t.Gender=@Gender";
var p1 = context.Persons.SqlQuery(sql, new MySqlParameter("@Gender", 1)).ToList();
// 下面这种更简单的写法相当于上面两句,EF 会自动将其转换为参数化查询
var p2 = context.Persons.SqlQuery("SELECT t.* FROM Person t WHERE t.Gender={0}", 1).ToList();
}
- 3、只查询部分可选字段,示例如下:
using (var context = new DemoDbContext()) {
var persons = context.Database.SqlQuery<MiniPerson>("SELECT t.ID,t.Name FROM Person t").ToList();
}
注意:这里用的是MiniPerson
类,而不是模型类Persons
,因为用模型类时,查询返回的字段必须与其模型中的字段对应,而用非模型类时则没有这个限制,EF 会自动把值赋给相应的字段,并忽略其它字段,即便完全不匹配也不会报错。
- 4、统计表中的数据条数,示例如下:
using (var context = new DemoDbContext()) {
var count = context.Database.SqlQuery<Int32>("SELECT COUNT(1) FROM Person").SingleOrDefault();
}
其实 EF 的SqlQuery()
还支持调用存储过程,但实际开发中,一般最好不要存储过程。因为一旦用了存储过程,相比较得到的性能提升,往往付出的维护代价会更大,得不偿失。
3.3、EF6 执行原生 SQL 增删改
EF6 调用增删改等命令语句的方法是ExecuteSqlCommand()
,示例如下:
using (var context = new DemoDbContext()) {
context.Database.ExecuteSqlCommand("INSERT INTO Person VALUES(DEFAULT,'小明',NOW(),1)");
context.Database.ExecuteSqlCommand("UPDATE Person SET Name='小王' WHERE ID=8");
context.Database.ExecuteSqlCommand("DELETE FROM Person WHERE ID=14");
}
一般用 EF 就是为了不写 SQL 语句,尤其是大多数时候不会造成性能问题的增删改语句,所以使用ExecuteSqlCommand()
的概率是比较低的。
3.4、EF6 不推荐的 CRUD 写法
有些朋友通过别人的帖子发现直接更改实体状态也能修改数据,然后就一直这么用。但如果你不是很了解 EF 的实体状态管理机制,就很可能会给自己挖坑,所以一般不推荐这种 CRUD 的写法。
我多次看到网上有人问诸如 EF 改了数据保存报错之类的问题,基本都是他自己还没搞清楚 EF 各个实体状态的含义,然后就在那儿强制更改实体状态,然后遇到坑自己还解决不了。这种做法有可能还会破坏 EF 的乐观并发控制,而且有些版本也不支持这种做法。下面给出两个负面案例:
- 1、不推荐的修改写法,会更新所有字段,示例如下:
using (var context = new DemoDbContext()) {
context.Database.Log = Console.WriteLine;
var p = new Person() { ID = 3, Name = "Andy" };
context.Entry(p).State = EntityState.Modified;
context.SaveChanges(); // 返回受影响行数 1
}
上面的代码会生成 1 条 UPDATE 语句。
- 2、不推荐的删除写法,示例如下:
using (var context = new DemoDbContext()) {
var p = new Person() { ID = 1 };
context.Entry(p).State = EntityState.Deleted;
context.SaveChanges(); // 返回受影响行数 1
}
上面的代码会生成 1 条 DELETE 语句。
3.5、EF6 性能优化
- 1、非跟踪查询 AsNoTracking
默认情况下,EF 会一直跟踪实体的状态,这也是为什么当我们调用SaveChanges()
的时候,EF 能够把最终的数据状态准确提交到数据库的原因。但有些时候,我们查询出数据只是为了做展示,并不需要修改或删除,这时候就可以调用AsNoTracking()
来使得对象为 Detached 状态,之后 EF 就不再跟踪这个对象状态了,在合适的场景下能显著提升性能。
using (var context = new DemoDbContext()) {
// 查询所有人并且不跟踪他们的状态
var p1 = context.Persons.AsNoTracking().ToList();
// 查询部分人并且不跟踪他们的状态
var p2 = context.Persons.Where(i => i.NationID == 1).AsNoTracking().ToList();
}
- 2、EF 默认是开启了 LoayLazy 的,别手贱关了就行。如下是默认配置:
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
3.6、EF6 开发及调试技巧
- 1、如果想知道 EF 会执行什么 SQL 语句,比如是控制台项目,在执行代码块中增加如下语句即可:
context.Database.Log = Console.WriteLine;
- 2、如果是自己测试,可以让 EF 每次都根据代码更新数据库,在上下文构造函数中增加如下代码即可:
// 当数据库模型发生改变时,则删除当前数据库,重建新的数据库(实际开发中永远不要这么写,太危险了)
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EFDbContext>());
或者在 CRUD 代码块中加入如下代码,仅当数据库不存在时,才由 EF 创建数据库:
context.Database.CreateIfNotExists();
4、总结
本文主要讲解了如何快速上手 EF6 和基本的 CRUD 操作。用 .NET 技术的博友都知道,如今 .NET 阵营除了经典的 .NET Framework 之外,还有一个开源版的 .NET Core。对应的,EF 也适时地推出了 EF Core 版,如果你的项目是 .NET 的,那就继续用 EF6 吧,毕竟是久经考验的版本,而 EF Core 是全新开发的,更适合 .NET Core 类型的项目。而且官方也说从 EF6 到 EF Core 是移植而不是升级。
4.1、MySQL 官方组件的用途说明
- 1、mysql-connector-net:MySQL Connector/NET 是 MySQL 官方的 .NET 驱动程序,或者说是 MySQL for .NET 的客户端开发包,其中包含了 .NET 连接 MySQL 所必须的 dll 文件。
- 2、mysql-for-visualstudio:6.7 以下版本的驱动中会包含该组件,它的作用是在通过 VS 建立实体模型时,在数据源中增加 MySQL 类型选项。如果只用 Code First,那么就不需要该组件了。
- 3、mysql-connector-odbc:MySQL Connector/ODBC 使得用户可以通过 ODBC(Open Database Connectivity,开放数据库互联)来连接 MySQL 服务器。
4.2、本文 Demo 的代码补充说明
- 文中的 Nation 实体定义如下:
public class Nation {
public Int32 ID { get; set; }
public String Name{ get; set; }
}
- 文中的 MiniPerson 类定义如下:
public class MiniPerson {
public Int32 ID { get; set; }
public String Name { get; set; }
}
本文链接:http://www.cnblogs.com/hanzongze/p/ef6-trial-report.html
版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!