公司的项目中用的 ORM 是 Dapper,代码中充斥着大量的 SQL 语句,为了少写 SQL 语句,领导让我把 EF6 也加进去看会不会有问题。按照指示,我在新的代码分支引入了 EF6 并做了 CRUD 的测试,结论是混合使用 Dapper 和 EF6 没问题。为了让团队中没用过 EF 的同事也能快速上手 EF,我把我的试用记录重新整理了一下,于是乎就有了本文。
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 还可以自定义表的 Schemapublic 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; }}
定义实体的注意事项:
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>(); }}
定义上下文的注意事项:
base("ConnectionString")
base("name=ConnectionString")
base(new MySqlConnection("..."), false)
using (var context = new DemoDbContext()) { var p = new Person() { Name = "Andy", Gender = 1 }; context.Persons.Add(p); context.SaveChanges(); // 返回受影响行数 1}
上面的代码会生成 1 条 INSERT 语句和 1 条 SELECT 语句。
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 语句。
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; }}
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();}
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();}
using (var context = new DemoDbContext()) { var persons = context.Persons.Include(p => p.Nation).ToList();}
上面的代码会生成 1 条内连接 SELECT 语句。
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 语句,数据不存在时会报错。
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 语句。
using (var context = new DemoDbContext()) { var p = new Person() { ID = 1 }; context.Persons.Attach(p); context.Persons.Remove(p); context.SaveChanges(); // 返回受影响行数}
上面的代码会生成 1 条 DELETE 语句,数据不存在时会报错。
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(); }}
技术好的人经常讲业务场景,相反,有些技术差的人却喜欢不由分说的吐槽那些他根本就没搞懂的技术。在 .NET 圈子里,有人对 EF 是爱不释手,也有人对 EF 是各种吐槽。
我很喜欢的一句话是:“没有不好的技术,只有没被用好的技术”,我的理解是任何技术都有局限性,作为程序员,我们要做的是结合实际业务场景来选用最合适的技术。要想在项目中更好的运用 EF,就得更多的了解 EF 技术,本节就来分享一下我试用 EF6 过程中的一些收获。
为什么说 EF 的三种模式是传说呢?因为新版的 EF 默认只支持 Code First 这一种模式了。要想用 Database First 或 Model First 还得把 Visual Studio 降级到 VS10 或 VS12 才行,实在没必要,下面简单罗列下每种模式的特点:
总会有些时候,我们为了性能或者其它各种各样的缘故,而不得不写 SQL 语句,EF 提供了直接执行 SQL 语句的方法SqlQuery()
。
using (var context = new DemoDbContext()) { var persons = context.Persons.SqlQuery("SELECT * FROM Person").ToList();}
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();}
using (var context = new DemoDbContext()) { var persons = context.Database.SqlQuery<MiniPerson>("SELECT t.ID,t.Name FROM Person t").ToList();}
注意:这里用的是MiniPerson
类,而不是模型类Persons
,因为用模型类时,查询返回的字段必须与其模型中的字段对应,而用非模型类时则没有这个限制,EF 会自动把值赋给相应的字段,并忽略其它字段,即便完全不匹配也不会报错。
using (var context = new DemoDbContext()) { var count = context.Database.SqlQuery<Int32>("SELECT COUNT(1) FROM Person").SingleOrDefault();}
其实 EF 的SqlQuery()
还支持调用存储过程,但实际开发中,一般最好不要存储过程。因为一旦用了存储过程,相比较得到的性能提升,往往付出的维护代价会更大,得不偿失。
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()
的概率是比较低的。
有些朋友通过别人的帖子发现直接更改实体状态也能修改数据,然后就一直这么用。但如果你不是很了解 EF 的实体状态管理机制,就很可能会给自己挖坑,所以一般不推荐这种 CRUD 的写法。
我多次看到网上有人问诸如 EF 改了数据保存报错之类的问题,基本都是他自己还没搞清楚 EF 各个实体状态的含义,然后就在那儿强制更改实体状态,然后遇到坑自己还解决不了。这种做法有可能还会破坏 EF 的乐观并发控制,而且有些版本也不支持这种做法。下面给出两个负面案例:
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 语句。
using (var context = new DemoDbContext()) { var p = new Person() { ID = 1 }; context.Entry(p).State = EntityState.Deleted; context.SaveChanges(); // 返回受影响行数 1}
上面的代码会生成 1 条 DELETE 语句。
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();}
this.Configuration.ProxyCreationEnabled = true;this.Configuration.LazyLoadingEnabled = true;
context.Database.Log = Console.WriteLine;
// 当数据库模型发生改变时,则删除当前数据库,重建新的数据库(实际开发中永远不要这么写,太危险了)Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EFDbContext>());
或者在 CRUD 代码块中加入如下代码,仅当数据库不存在时,才由 EF 创建数据库:
context.Database.CreateIfNotExists();
本文主要讲解了如何快速上手 EF6 和基本的 CRUD 操作。用 .NET 技术的博友都知道,如今 .NET 阵营除了经典的 .NET Framework 之外,还有一个开源版的 .NET Core。对应的,EF 也适时地推出了 EF Core 版,如果你的项目是 .NET 的,那就继续用 EF6 吧,毕竟是久经考验的版本,而 EF Core 是全新开发的,更适合 .NET Core 类型的项目。而且官方也说从 EF6 到 EF Core 是移植而不是升级。
public class Nation { public Int32 ID { get; set; } public String Name{ get; set; } }
public class MiniPerson { public Int32 ID { get; set; } public String Name { get; set; }}
本文链接:http://www.cnblogs.com/hanzongze/p/ef6-trial-report.html
版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!