APS.NET MVC + EF (02)---深刻理解ADO.NET Entity Framework

2.7 深刻理解Entity Framework

性能问题几乎是一切ORM框架的通病,对于EF来讲,引发性能低的缘由主要在如下几个方面。sql

  • 复杂的对象管理机制
    为了在
    .NET中更好地管理模型对象,EF提供了一套内部机制管理和跟踪对象的状态,保持对象的一致性,带来方便的同时,下降了性能。
  • 高度封装的执行机制

    在EF应用中,编写的任何一个查询表达式都须要通过分析,解析成SQL语句,而后调用底层的ADO.NET Providers去执行。直接执行SQL语句相比,性能上有必定的下降。 数据库

  • 低效的SQL语句

    EF采用映射机制将对象操做转换为SQL语句,SQL语句通常是基于标准模板生成的,不会进行特殊的优化,这与直接针对业务编写的SQL语句去操做数据相比,效率通常会打折扣,特别是复杂的数据库操做。 缓存

       

Linq查询最终生成的SQL语句是什么样的?咱们可使用ToString()方法直接输出T-SQL代码。如示例10所示。 框架

示例10 ide

using (MySchoolContext db = new MySchoolContext()) 性能

{ 优化

var result = db.Student.Where(n => n.StudentName.Contains("张")); ui

Console.WriteLine(result); spa

}3d

运行结果如图2-10所示。

图2-10 Linq查询生成的SQL语句

固然,EF自己对性能有一系列的优化措施,会使用这写手段的前提是对EF的执行机制有足够的了解。

   

2.7.1 EF的状态管理

在程序中实现数据的增、删、改操做,EF会监控这些状态的变化,在执行SaveChange()方法时,会根据对象状态的变化执行相应的操做。如示例11所示。

示例11

using (MySchoolContext db = new MySchoolContext())

{

Grade grade = new Grade() { GradeName = "Y3" };

//输出当前对象状态

Console.WriteLine(db.Entry(grade).State);

   

db.Grade.Add(grade);

Console.WriteLine(db.Entry(grade).State);

   

db.SaveChanges();

Console.WriteLine(db.Entry(grade).State);

}

   

示例11中,经过Entry()方法获取模型状态,该方法是DbContext类的成员方法,定义以下:

public DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)

返回类型 DbEntityEntry<T> 封装了对象状态相关的信息。经常使用成员如表3-2所示。

表3-2 DbEntityEntry 的主要成员

方法或属性

说明

CurrentValues

获取由此对象表示的跟踪实体的当前属性值

OriginalValues

获取由此对象表示的跟踪实体的原始属性值

State

获取或设置实体的状态

Reload()

从数据库从新加载对象的值

其中,State属性是一个EntityState枚举类型,其取值以下:

Detached:表示对象存在,但没有被跟踪

Unchanged:表示对象还没有通过修改

Added:表示对象为新对象,而且已添加到对象上下文

Deleted:对象已从对象上下文中删除

Modified:表示对象的一个标量属性已更改

   

经过设置实体的State属性也能够实现对数据的操做,如:

using (MySchoolContext db = new MySchoolContext())

{

Grade grade = new Grade() { GradeName = "Y3" };

db.Entry(grade).State = EntityState.Added;

db.SaveChanges( );

}

   

除了须要增删改的对象会受状态管理机制管理外,经过EF查询的数据也默认会进行状态管理。能够经过两种方式指定查询不进行状态管理。

方式一:使用AsNoTracking()方法,如示例12所示。

示例12

using (MySchoolContext db = new MySchoolContext())

{

var result = db.Student.AsNoTracking().FirstOrDefault();

Console.WriteLine(db.Entry(result).State);

}

   

方式二:设置Configuration.AutoDetectChangesEnabled 属性为false,如示例13。

示例13

using (MySchoolContext db = new MySchoolContext())

{

//禁用自动跟踪变化

db.Configuration.AutoDetectChangesEnabled = false;

for (int i = 0; i < 5000; i++)

{

var stu = new Student() { StudentName = "alex",

GradeId = 1, Age = 20 };

db.Student.Add(stu);

}

db.SaveChanges();

}

   

在使用EF修改或删除数据时,必须先查询对象,而后再对其进行修改或删除。然而现实开发中不少状况都是经过主键删除一条数据。咱们能够经过实体的状态特性来进行优化。

示例14

using (MySchool1Entities entities = new MySchool1Entities())

{

//建立替身对象

var stu = new Student { StudentNo = "10001" };

//给实体附加到上下文中

entities.Student.Attach(stu);

//删除

entities.Student.Remove(stu);

entities.SaveChanges();

}

代码中的Attach()方法能够将EntityState.Unchangee状态的对象附加到上下文中。

   

2.7.2 延迟加载和贪婪加载

  1. 延迟加载

    又称为懒加载,只有每次调用子实体(外键所在的实体)的时候,才去查询数据库, 主表数据加载的时候,不去查询外键所在的从表。

    实现延迟加载须要知足两个条件:

  • poco类是public且不能为sealed。
  • 导航属性须要标记为Virtual。

也能够关闭延迟加载,方法是:

db.Configuration.LazyLoadingEnabled = false;

关闭延迟加载后,查询主表数据时候,主表的中从表实体为null。

示例15

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("---------------- 01-延迟加载 ---------------");

//EF默认就是延迟加载,默认下面的语句就是true,因此下面语句注释没有任何影响

db.Configuration.LazyLoadingEnabled = true;

   

var list = db.Student.ToList(); //此处加载的数据,没有对从表进行任何查询操做

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId, stu.studentName);

//下面调用导航属性(一对一的关系) 每次调用时,都要去查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

   

  1. 贪婪加载

    又名:当即加载、贪婪加载、预加载。查询主表的时候经过Include()方法一次性将数据查询了出来,在调用从表数据的时候,从缓存中读取,无须查询数据库。

    实现方式:

  • 先关闭延迟加载:db.Configuration.LazyLoadingEnabled = false;
  • 查询主表的同时经过Include把从表数据也查询出来。

示例16

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("------------------- 03-当即加载 ------------------");

   

//1.关闭延迟加载

db.Configuration.LazyLoadingEnabled = false;

   

//2. 获取主表数据的同时,经过Include将从表中的数据也所有加载出来

var list = db.Student.Include("StudentAddress").ToList();

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId,

stu.studentName);

//这里获取从表中的数据,均是从缓存中获取,无需查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

   

  1. 显示加载

    关闭了延迟加载,单纯查询了主表数据,这个时候须要从新查询从表数据,就要用到显式加载了。

    使用步骤:

    ①:单个实体用:Reference

    ②:集合用:Collection

    ③:最后须要Load一下

    示例17

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("----------------- 04-显式加载 ------------------");

//1.关闭延迟加载

db.Configuration.LazyLoadingEnabled = false;

   

//2.此处加载的数据,不含从表中的数据

var list = db.Student.ToList();

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId,

stu.studentName);

//3.下面的这句话,能够开启从新查询一次数据库

//3.1 单个属性的状况用Refercence

db.Entry<Student>(stu).Reference(c => c.StudentAddress).Load();

//3.2 集合的状况用Collection

//db.Entry<Student>(stu).Collection(c => c.StudentAddress).Load();

   

//下面调用导航属性(一对一的关系) 每次调用时,都要去查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

   

  1. 小结

    何时使用延迟加载,何时又使用贪婪加载呢?

    延迟加载只有在须要使用数据时加载,若是不须要使用实体的关联数据,可使用延迟加载。使用贪婪加载适用于预先了解要使用什么数据的状况,利用这种方式一次性加载数据,能够减小数据库访问次数。

    从实际状况来看,使用默认的延迟加载就能够了,2次或3次的数据库查询是能够接受的。而循环中屡次读取数据库,能够考虑使用贪婪加载。

2.7.3 本地缓存

在使用EF时,有时会屡次使用一个查询结果,如示例18所示。

示例18

using (MySchoolEntities db = new MySchoolEntities())

{

//查询所有学生

IQueryable<Student> stus = db.Student;

Console.WriteLine("所有学生姓名:");

foreach (var stu in stus)

{

Console.WriteLine("学生姓名:{0}",stu.StudentName);

}

//查询并输出学生人数

Console.WriteLine("学生人数为:{0}",db.Student.Count());

}

   

示例18中会产生两次查询,但从需求来看,彻底没有必要,由于第二次彻底能够利用第一次查询的结果。事实上,彻底可使用EF的缓存功能,直接利用缓存的结果,DbSet<T>的Local属性正是用于提供缓存的数据。

示例18中,将"db.Student.Count()"替换为"db.Student.Local.Count()",这样就不会产生新的查询了。

另外,DbSet<T>提供了 Find()方法,用于经过主键查找实体,其查询速度比First()和FirstOrDefault()方法快的多,而且若是相应的实体已经被DbContext缓存,EF会在缓存中直接返回对应的实体,而不会执行数据库访问。

   

2.7.4 EF中的事务

EF中的事务主要分为三类,分别是SaveChanges、DbContextTransaction 和

TransactionScope。

  1. SaveChanges事务

    在前面内容中,SaveChanges一次性将本地缓存中全部的状态变化一次性提交到数据库,这就是一个事务,要么统一成功,要么统一回滚。

    示例19

using (DbContext db = new CodeFirstModel())

{

//增长

TestInfor t1 = new TestInfor()

{

id = Guid.NewGuid().ToString("N"),

txt1 = "txt1",

txt2 = "txt2"

};

db.Set<TestInfor>().Add(t1);

//删除

TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();

if (t2 != null)

{

db.Set<TestInfor>().Remove(t2);

}

//修改

TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();

t3.txt2 = "我是李马茹23";

   

//SaveChanges事务提交

int n = db.SaveChanges();

Console.WriteLine("数据做用条数:" + n);

}

   

示例19中,若是三个操做中有任意一个出现错误,就会回滚,结果n为0。

   

  1. DbContextTransaction 事务

    使用场景:EF调用SQL语句的时候使用该事务、 多个SaveChanges的状况。

    示例20

using (DbContext db = new CodeFirstModel())

{

DbContextTransaction trans = null;

try

{

//开启事务

trans = db.Database.BeginTransaction();

//增长

string sql1 = @"insert into TestInfor values(@id,@txt1,@txt2)";

SqlParameter[] pars1 ={

new SqlParameter("@id",Guid.NewGuid().ToString("N")),

new SqlParameter("@txt1","txt11"),

new SqlParameter("@txt2","txt22")

};

db.Database.ExecuteSqlCommand(sql1, pars1);

//删除

string sql2 = @"delete from TestInfor where id=@id";

SqlParameter[] pars2 ={

new SqlParameter("@id","22")

};

db.Database.ExecuteSqlCommand(sql2, pars2);

//修改

string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";

SqlParameter[] pars3 ={

new SqlParameter("@id","3"),

new SqlParameter("@txt1","二狗子")

};

db.Database.ExecuteSqlCommand(sql3, pars3);

   

//提交事务

trans.Commit();

Console.WriteLine("事务成功了");

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

trans.Rollback(); //回滚

   

}

finally

{

//也能够把该事务写到using块中,让其本身托管,就不须要手动释放了

trans.Dispose();

}

}

   

DbContextTransaction事务也适用于多个SaveChanges的状况。

示例21

using (DbContext db = new CodeFirstModel())

{

//自动脱管,不须要手动释放

using (DbContextTransaction trans = db.Database.BeginTransaction())

{

try

{

TestInfor t1 = new TestInfor()

{

id = Guid.NewGuid().ToString("N"),

txt1 = "111111111",

txt2 = "222222222222"

};

db.Entry(t1).State = EntityState.Added;

db.SaveChanges();

   

TestInfor t2 = new TestInfor()

{

id = Guid.NewGuid().ToString("N") + "123",

txt1 = "111111111",

txt2 = "222222222222"

};

db.Entry(t2).State = EntityState.Added;

db.SaveChanges();

   

trans.Commit();

}

catch (Exception)

{

trans.Rollback();

}

}

}

   

  1. TransactionScope事务

    该种事务适用于多数据库链接的状况,在此不作介绍,请自行查阅相关资料。

   

2.7.5 从实体框架回归SQL

EF虽然自己有不少优化机制,但和直接使用ADO.NET相比,仍是有必定的性能差距,所以EF在DbContext类的Database属性里提供了ExecuteSqlCommand()和SqlQuery()两个方法,用来直接访问数据库。

  1. ExecuteSqlCommand()

    方法的定义以下:

    public int ExecuteSqlCommand(string sql, params object[] parameters)

    用来执行增、删、改操做,返回结果为受影响行数。

  2. SqlQuery()

    方法的定义以下:

    public DbRawSqlQuery<T> SqlQuery<T>(string sql, params object[] parameters);

    用来执行查询操做,返回结果是一个集合。

示例22

using (MySchool1Entities db = new MySchool1Entities())

{

//执行update语句

string sql = "update grade set gradeName=@gradeName where

gradeId=@gradeId";

SqlParameter[] ps =

{

new SqlParameter("@gradeName","第二学年"),

new SqlParameter("@gradeId",3)

};

int result=db.Database.ExecuteSqlCommand(sql, ps);

if (result>0)

{

Console.WriteLine("数据更新完成!");

}

//执行查询语句

sql = "select * from from student where studentNo=@stuNo";

ps = new SqlParameter[] { new SqlParameter("@stuNo", "S1001234") };

var stu = db.Database.SqlQuery<Student>(sql, ps);

Console.WriteLine(stu.ToList()[0]);

}

   

2.8 封装EFDAL

   

 1     public class BaseDAL<T> where T:class
 2  {  3         private DbContext db  4  {  5             get
 6  {  7                 DbContext dbContext = CallContext.GetData("dbContext") as DbContext;  8                 if (dbContext == null)  9  {  10                     dbContext = new MySchoolContext();  11                     CallContext.SetData("dbContext", dbContext);  12  }  13                 return dbContext;  14  }  15  }  16 
 17         /// <summary>
 18         /// 执行增长,删除,修改操做(或调用存储过程)  19         /// </summary>
 20         /// <param name="sql"></param>
 21         /// <param name="pars"></param>
 22         /// <returns></returns>
 23         public int ExecuteSql(string sql, params SqlParameter[] pars)  24  {  25             return db.Database.ExecuteSqlCommand(sql, pars);  26  }  27 
 28         /// <summary>
 29         /// 执行查询操做  30         /// </summary>
 31         /// <typeparam name="T"></typeparam>
 32         /// <param name="sql"></param>
 33         /// <param name="pars"></param>
 34         /// <returns></returns>
 35         public List<T> ExecuteQuery(string sql, params SqlParameter[] pars)  36  {  37             return db.Database.SqlQuery<T>(sql, pars).ToList();  38  }  39 
 40         /// <summary>
 41         /// 添加  42         /// </summary>
 43         /// <param name="model"></param>
 44         /// <returns></returns>
 45         public int Add(T model)  46  {  47             db.Set<T>().Add(model);  48             return db.SaveChanges();  49  }  50 
 51         /// <summary>
 52         /// 删除(适用于先查询后删除的单个实体)  53         /// </summary>
 54         /// <param name="model">须要删除的实体</param>
 55         /// <returns></returns>
 56         public int Del(T model)  57  {  58             db.Set<T>().Attach(model);  59             db.Set<T>().Remove(model);  60             return db.SaveChanges();  61  }  62 
 63         /// <summary>
 64         /// 根据条件删除(支持批量删除)  65         /// </summary>
 66         /// <param name="delWhere">传入Lambda表达式(生成表达式目录树)</param>
 67         /// <returns></returns>
 68         public int DelBy(Expression<Func<T, bool>> delWhere)  69  {  70             var listDels = db.Set<T>().Where(delWhere);  71             foreach(var model in listDels)  72  {  73                 db.Set<T>().Attach(model);  74                 db.Set<T>().Remove(model);  75  }  76             return db.SaveChanges();  77  }  78 
 79         /// <summary>
 80         /// 修改  81         /// </summary>
 82         /// <param name="model">修改后的实体</param>
 83         /// <returns></returns>
 84         public int Modify(T model)  85  {  86             db.Entry(model).State = EntityState.Modified;  87             return db.SaveChanges();  88  }  89 
 90         /// <summary>
 91         /// 批量修改  92         /// </summary>
 93         /// <param name="model">要修改实体中 修改后的属性 </param>
 94         /// <param name="whereLambda">查询实体的条件</param>
 95         /// <param name="proNames">lambda的形式表示要修改的实体属性名</param>
 96         /// <returns></returns>
 97         public int ModifyBy(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames)  98  {  99             List<T> listModifes = db.Set<T>().Where(whereLambda).ToList(); 100             Type t = typeof(T); 101             List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList(); 102             Dictionary<string, PropertyInfo> dicPros = new Dictionary<string, PropertyInfo>(); 103             proInfos.ForEach(p =>
104  { 105                 if (proNames.Contains(p.Name)) 106  { 107  dicPros.Add(p.Name, p); 108  } 109  }); 110             foreach (string proName in proNames) 111  { 112                 if (dicPros.ContainsKey(proName)) 113  { 114                     PropertyInfo proInfo = dicPros[proName]; 115                     object newValue = proInfo.GetValue(model, null); 116                     foreach (T m in listModifes) 117  { 118                         proInfo.SetValue(m, newValue, null); 119  } 120  } 121  } 122             return db.SaveChanges(); 123  } 124 
125         /// <summary>
126         /// 根据条件查询 127         /// </summary>
128         /// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
129         /// <returns></returns>
130         public IQueryable<T> GetListBy(Expression<Func<T, bool>> whereLambda) 131  { 132             return db.Set<T>().Where(whereLambda); 133  } 134         /// <summary>
135         /// 根据条件排序和查询 136         /// </summary>
137         /// <typeparam name="Tkey">排序字段类型</typeparam>
138         /// <param name="whereLambda">查询条件</param>
139         /// <param name="orderLambda">排序条件</param>
140         /// <param name="isAsc">升序or降序</param>
141         /// <returns></returns>
142         public IQueryable<T> GetListBy<Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 143  { 144             if (isAsc) 145  { 146                 return db.Set<T>().Where(whereLambda).OrderBy(orderLambda); 147  } 148             else
149  { 150                 return db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda); 151  } 152  } 153         /// <summary>
154         /// 分页查询 155         /// </summary>
156         /// <typeparam name="Tkey">排序字段类型</typeparam>
157         /// <param name="pageIndex">页码</param>
158         /// <param name="pageSize">页容量</param>
159         /// <param name="whereLambda">查询条件</param>
160         /// <param name="orderLambda">排序条件</param>
161         /// <param name="isAsc">升序or降序</param>
162         /// <returns></returns>
163         public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 164  { 165 
166             IQueryable<T> list = null; 167             if (isAsc) 168  { 169                 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda) 170                .Skip((pageIndex - 1) * pageSize).Take(pageSize); 171  } 172             else
173  { 174                 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda) 175               .Skip((pageIndex - 1) * pageSize).Take(pageSize); 176  } 177             return list; 178  } 179         /// <summary>
180         /// 分页查询输出总行数 181         /// </summary>
182         /// <typeparam name="Tkey">排序字段类型</typeparam>
183         /// <param name="pageIndex">页码</param>
184         /// <param name="pageSize">页容量</param>
185         /// <param name="whereLambda">查询条件</param>
186         /// <param name="orderLambda">排序条件</param>
187         /// <param name="isAsc">升序or降序</param>
188         /// <returns></returns>
189         public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, out int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 190  { 191             IQueryable<T> list = null; 192             rowCount = db.Set<T>().Where(whereLambda).Count(); 193             if (isAsc) 194  { 195                 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda) 196                    .Skip((pageIndex - 1) * pageSize).Take(pageSize); 197  } 198             else
199  { 200                 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda) 201                  .Skip((pageIndex - 1) * pageSize).Take(pageSize); 202  } 203             return list; 204  } 205 
206 
207     }
View Code

 

 

<<BaseDAL.cs>>

相关文章
相关标签/搜索