EF 原理及SQL 监控
EF 会自动把Where()、OrderBy()、Select()等这些编译成“表达式树(Expression Tree)”,然后会把表达式树翻译成SQL 语句去执行。(编译原理,AST)因此不是“把数据都取到内存中,然后使用集合的方法进行数据过滤”,因此性能不会低。但是如果这个操作不能被翻译成SQL语句,则或者报错,或者被放到内存中操作,性能就会非常低。
-
怎么查看真正执行的SQL是什么样呢?
DbContext有一个Database属性,其中的Log属性,是Action委托类型,也就是可以指向一个void A(string s)方法,其中的参数就是执行的SQL语句,每次EF执行SQL语句的时候都会执行Log。因此就可以知道执行了什么SQL。
EF的查询是“延迟执行”的,只有遍历结果集的时候才执行select 查询,ToList()内部也是遍历结果集形成List。
查看Update操作,会发现只更新了修改的字段。
-
观察一下前面学学习时候执行的SQL是什么样的。Skip().Take()被翻译成了?Count()被翻译成了?
var result = ctx.Persons.Where(p => p.Name.StartsWith("inlett"));//看看翻译成了什么? var result = ctx.Persons.Where(p => p.Name.Contains("com")); var result = ctx.Persons.Where(p => p.Name.Length>5); var result = ctx.Persons.Where(p => p.CreateDateTime>DateTime.Now); long[] ids = { 2,5,6};//不要写成int[] var result = ctx.Persons.Where(p => ids.Contains(p.Id));
-
EF中还可以多次指定where来实现动态的复合检索:
//必须写成IQueryable<Person>,如果写成IEnumerable 就会在内存中取后续数据 IQueryable<Person> items = ctx.Persons;//为什么把IQueryable<Person>换成var 会编译出错 items = items.Where(p=>p.Name=="inlett"); items = items.Where(p=>p.Id>5);
查看一下生成的SQL语句。
-
EF是跨数据库的,如果迁移到MYSQL上,就会翻译成MYSQL的语法。要配置对应数据库的Entity Framework Provider。
-
细节:
每次开始执行的__MigrationHistory等这些SQL语句是什么?是DBMigration用的,也就是由EF帮我们建数据库,现在我们用不到,用下面的代码禁用:
Database.SetInitializer(null);
XXXDbContext就是项目DbContext的类名。一般建议放到XXXDbContext构造函数中。注意这里的Database
是System.Data.Entity下的类,不是DbContext的Database属性。如果写到DbContext中,最好用上全名,防止出错。
执行原始SQL
不要“手里有锤子,到处都是钉子”在一些特殊场合,需要执行原生SQL。
执行非查询语句,调用DbContext的Database属性的ExecuteSqlCommand方法,可以通过占位符的方式传递参数:
ctx.Database.ExecuteSqlCommand("update T_Persons set Name={0},CreateDateTime=GetDate()","YLT.com");
占位符的方式不是字符串拼接,经过观察生成的SQL语句,发现仍然是参数化查询,因此不会有SQL注入漏洞。
执行查询:
var q1 = ctx.Database.SqlQuery<Item1>("select Name,Count(*) Count from T_Persons where Id>{0} and CreateDateTime<={1} group by Name",2, DateTime.Now); //返回值是DbRawSqlQuery<T>类型,也是实现IEnumerable 接口
foreach(var item in q1)
{
Console.WriteLine(item.Name+":"+item.Count);
}
class Item1
{
public string Name { get; set; }
public int Count { get; set; }
}
类似于ExecuteScalar的操作比较麻烦:
int c = ctx.Database.SqlQuery<int>("select count(*) from T_Persons").SingleOrDefault();
不是所有lambda 写法都能被支持
下面想把Id转换为字符串比较一下是否为"3"(别管为什么):
var result = ctx.Persons.Where(p => Convert.ToString(p.Id)=="3");
运行会报错(也许高版本支持了就不报错了),这是一个语法、逻辑上合法的写法,但是EF目前无法把他解析为一个SQL语句。
出现“System.NotSupportedException”异常一般就说明你的写法无法翻译成SQL语句
想获取创建日期早于当前时间一小时以上的数据:
var result = ctx.Persons.Where(p => (DateTime.Now - p.CreateDateTime).TotalHours>1);
同样也可能会报错。
怎么解决?
尝试其他替代方案(没有依据,只能乱试):
var result = ctx.Persons.Where(p => p.Id==3);
EF中提供了一个SQLServer专用的类SqlFunctions,对于EF不支持的函数提供了支持,比如:
var result = ctx.Persons.Where(p =>SqlFunctions.DateDiff("hour",p.CreateDateTime,DateTime.Now)>1);
EF对象的状态
简介
为什么查询出来的对象Remove()、再SaveChanges()就会把数据删除。而自己new一个Person()对象,然后Remove()不行?为什么查询出来的对象修改属性值后、再SaveChanges()就会把数据库中的数据修改。
因为EF会跟踪对象状态的改变。
EF中中对象有五个状态:Detached(游离态,脱离态)、Unchanged(未改变)、Added(新增)、Deleted(删除)、Modified(被修改)。
Add()、Remove()修改对象的状态。所有状态之间几乎都可以通过:Entry§.State=xxx的方式进行强制状态转换。
通过代码来演示一下。这个状态转换图没必要记住,了解即可。
状态改变都是依赖于Id的(Added除外)
应用(*)
当SavaChanged()方法执行期间,会查看当前对象的EntityState的值,决定是去新增(Added)、修改Modified)、删除(Deleted)或者什么也不做(UnChanged)。下面的做法不推荐,在旧版本中一些写法不被支持,到新版EF中可能也会不支持。
-
不先查询再修改再保存,而是直接更新部分字段的方法
var p = new Person(); p.Id = 2; ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged; p.Name = "adfad"; ctx.SaveChanges();
也可以:
var p = new Person(); p.Id = 5; p.Name = "yltedu"; ctx.Persons.Attach(p);//等价于ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged; ctx.Entry(p).Property(a => a.Name).IsModified = true; ctx.SaveChanges();
-
不先查询再Remove再保存,而是直接根据Id删除的方法:
var p = new Person(); p.Id = 2; ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted; ctx.SaveChanges();
注意下面的做法并不会删除所有Name=“ylt.com” 的,因为更新、删除等都是根据Id进行的:
var p = new Person(); p.Name = "yltedu.com"; ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted; ctx.SaveChanges();
上面其实是在:
delete * from t_persons where Id=0
EF优化的一个技巧
如果查询出来的对象只是供显示使用,不会修改、删除后保存,那么可以使用AsNoTracking()来使得查询出来的对象是Detached状态,这样对对象的修改也还是Detached状态,EF不再跟踪这个对象状态的改变,能够提升性能。
var p1 = ctx.Persons.Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
改成:
var p1 = ctx.Persons.AsNoTracking().Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
因为AsNoTracking()是DbQuery类(DbSet的父类)的方法,所以要先在DbSet后调用AsNoTracking()。