EF(七)---EF延迟加载

延迟加载(LazyLoad)

如果public virtual Class Class { get; set; }(实体之间的关联属性又叫做“导航属性(Navigation
Property)”)把virtual 去掉,那么下面的代码就会报空引用异常

var s = ctx.Students.First();
Console.WriteLine(s.Class.Name);

联想为什么?凭什么!!!
改成virtual观察SQL的执行。执行了两个SQL,先查询T_Students,再到T_Classes中查到对应的行。
这叫“延迟加载”(LazyLoad),只有用到关联的对象的数据,才会再去执行select 查询。注意延迟加载只在关联对象属性上,普通属性没这个东西。
注意:启用延迟加载需要配置如下两个属性(默认就是true,因此不需要去配置,只要别手贱设置为false 即可)

context.Configuration.ProxyCreationEnabled = true;
context.Configuration.LazyLoadingEnabled = true;

分析延迟加载的原理:打印一下拿到的对象的GetType(),再打印一下GetType().BaseType;我们发现拿到的对象其实是Student子类的对象。(如果和我这里结果不一致的话,说明:类不是public,没有关联的virtual 属性)
因此EF其实是动态生成了实体类对象的子类,然后override了这些virtual属性,类似于这样的
实现:

public class StudentProxy:Student
{
    private Class clz;
    public override Class Class
    {
        get
        {
            if(this.clz==null)
            {
                this.clz= ....//这里是从数据库中加载Class 对象的代码
            }
            return this.clz;
        }
     }
}

再次强调:如果要使用延迟加载,类必须是public,关联属性必须是virtual。
延迟加载(LazyLoad)的优点:用到的时候才加载,没用到的时候才加载,因此避免了一次性加载所有数据,提高了加载的速度。缺点:如果不用延迟加载,就可以一次数据库查询就可以把所有数据都取出来(使用join实现),用了延迟加载就要多次执行数据库操作,提高了数据库服务器的压力。
因此:如果关联的属性几乎都要读取到,那么就不要用延迟加载;如果关联的属性只有较小的概率(比如年龄大于7
岁的学生显示班级名字,否则就不显示)则可以启用延迟加载。这个概率到底是多少是没有一个固定的值,和数据、业务、技术架构的特点都有关系,这是需要经验和直觉,也需要测试和平衡的。
注意:启用延迟加载的时候拿到的对象是动态生成类的对象,是不可序列化的,因此不能直接放到进程外Session、Redis 等中,解决方法?

不延迟加载,怎么样一次性加

用EF永远都要把导航属性设置为virtual。又想方便(必须是virtual)又想效率高!

使用Include()方法:

var s = ctx.Students.Include("Class").First();

观察生成的SQL语句,会发现只执行一个使用join的SQL就把所有用到的数据取出来了。当然拿到的对象还是Student 的子类对象,但是不会延迟加载。(不用研究“怎么让他返回Student 对象”)

Include(“Class”)的意思是直接加载Student 的Class 属性的数据。注意只有关联的对象属性才可以用Include,普通字段不可以直接写"Class"可能拼写错误,如果用C#6.0,可以使用nameof语法解决问这个问题:

var s = ctx.Students.Include(nameof(Student.Class)).First();

也可以using System.Data.Entity;然后var s = ctx.Students.Include(e=>e.Class).First(); 推荐这种做法。
如果有多个属性需要一次性加载,也可以写多个Include:

var s = ctx.Students.Include(e=>e.Class) .Include(e=>e.Teacher).First();

如果Class对象还有一个School属性,也想把School对象的属性也加载,就要:

var s = ctx.Students.Include("Class").Include("Class. School").First(); 或者更好的
var s = ctx.Students.Include(nameof(Student.Class)).Include(nameof(Student.Class)+"."+nameof(Class.School)).First();

延迟加载的一些坑

  1. DbContext销毁后就不能再延迟加载了,因为数据库连接已经断开

    下面的代码最后一行会报错:

    Student s;
    using (MyDbContext ctx = new MyDbContext())
    {
       s = ctx.Students.First();
    }
    Console.WriteLine(s.Class.Name);
    

    两种解决方法:

    • 用Include,不延迟加载(推荐)

      Student s;
      using (MyDbContext ctx = new MyDbContext())
      {
          s = ctx.Students.Include(t=>t.Class).First();
      }
      Console.WriteLine(s.Class.Name);
      
    • 关闭前把要用到的数据取出来

      Class c;
      using (MyDbContext ctx = new MyDbContext())
      {
          Student s = ctx.Students.Include(t=>t.Class).First();\
          c = s.Class;
      }
      Console.WriteLine(c.Name);
      
  2. 两个取数据一起使用

    下面的程序会报错:已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。

    foreach(var s in ctx.Students)
    {
        Console.WriteLine(s.Name);
        Console.WriteLine(s.Class.Name);
    }
    
  3. 因为EF的查询是“延迟执行”的,只有遍历结果集的时候才执行select 查询,而由于延迟加载的存在到s.Class.Name也会再次执行查询。ADO.Net中默认是不能同时遍历两个DataReader。因此就报错。

    解决方法有如下

    • 允许多个DataReader 一起执行:在连接字符串上加上MultipleActiveResultSets=true,但只适用于SQL 2005以后的版本。其他数据库不支持。

    • 执行一下ToList(),因为ToList()就遍历然后生成List:

      foreach(var s in ctx.Students.ToList())
      {
          Console.WriteLine(s.Name);
          Console.WriteLine(s.Class.Name);
      }
      
    • 推荐做法:用Include预先加载:

      foreach(var s in ctx.Students.Include(e=>e.Class))
      {
          Console.WriteLine(s.Name);
          Console.WriteLine(s.Class.Name);
      }
      
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

IT-wanghanghang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值