标签:简单的 方便 完成 host down change 优先 mini 默认
公司的项目中用的 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 还可以自定义表的 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; }
}
定义实体的注意事项:
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
版权声明:本文为博客园博主 韩宗泽 原创,作者保留署名权!欢迎通过转载、演绎或其它传播方式来使用本文,但必须在明显位置给出作者署名和本文链接!个人博客,能力有限,若有不当之处,敬请批评指正,谢谢!
标签:简单的 方便 完成 host down change 优先 mini 默认
原文地址:https://www.cnblogs.com/hanzongze/p/ef6-trial-report.html