EF(四)-- EF原理及对象

EF 原理及SQL 监控

EF 会自动把Where()、OrderBy()、Select()等这些编译成“表达式树(Expression Tree)”,然后会把表达式树翻译成SQL 语句去执行。(编译原理,AST)因此不是“把数据都取到内存中,然后使用集合的方法进行数据过滤”,因此性能不会低。但是如果这个操作不能被翻译成SQL语句,则或者报错,或者被放到内存中操作,性能就会非常低。

  1. 怎么查看真正执行的SQL是什么样呢?

    DbContext有一个Database属性,其中的Log属性,是Action委托类型,也就是可以指向一个void A(string s)方法,其中的参数就是执行的SQL语句,每次EF执行SQL语句的时候都会执行Log。因此就可以知道执行了什么SQL。

    EF的查询是“延迟执行”的,只有遍历结果集的时候才执行select 查询,ToList()内部也是遍历结果集形成List。

    查看Update操作,会发现只更新了修改的字段。

  2. 观察一下前面学学习时候执行的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));
    
  3. 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语句。

  4. EF是跨数据库的,如果迁移到MYSQL上,就会翻译成MYSQL的语法。要配置对应数据库的Entity Framework Provider。

  5. 细节:

    每次开始执行的__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中可能也会不支持。

  1. 不先查询再修改再保存,而是直接更新部分字段的方法

    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();
    
  2. 不先查询再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()。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT-wanghanghang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值