• MVC系列学习(三)-EF的延迟加载


    1.什么叫延迟加载

    字面上可以理解为,一个动作本该立即执行的动作,没有立即执行

    2.从代码上理解

    static void Main(string[] args)
    {
        //执行该语句的时候,查看sql监视器,发现并没有生成sql语句
        IEnumerable<Student> stu = dbContext.Students.Where(s => s.Id == 1).Select(s => s);
        //只有当  使用的时候  ,才生成sql语句
        Student student = stu.FirstOrDefault();
    }

    只有对象被使用了,才生成sql语句

    3.寻找原因,什么原因导致延迟加载

    先理解两个Where()方法:

    a.集合的Where()

    List<string> listStr = new List<string>()
    {
        "A",
        "BB",
        "CCC"
    };
    string  bb = listStr.Where(s => s.Length == 2).Select(s => s).FirstOrDefault();

    转到Where的定义,发现 集合 中的Where方法 实际上是IEnumerable的扩展方法,该接口继承与IEnumerable

    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
    public interface IEnumerable<out T> : IEnumerable

    b.DbSet中的Where方法

    dbContext.Students是DbSet<Student>类型,所以此处说 DbSet的中Where方法

    var stu = dbContext.Students.Where(s => s.Id == 1);
    public partial class SchoolEntities : DbContext
    {
        public virtual DbSet<Student> Students { get; set; }
    }

    image

    dbContext.Students是DbSet<T>类型的,转到DbSet<T>定义看看

    此处的Where()是IQueryable的扩展方法,继承与IQueryable

    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
    public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
    public interface IQueryable : IEnumerable

    c.IEnumerable与IQueryable的区别

    var s1 = dbContext.Students.Where(s => s.Id == 1);
    var s2 = s1.Where(s => s.Age > 0);
    var s3 = s2.Select(s => s).FirstOrDefault();

    用sql监视器查看,发现总共就执行了一次sql查询

    得出结论:

    1.实现EF延迟加载 的 实际上是IQueryable上的扩展方法,更具体的话是DbQuery类型来实现的
    2.IEnumerable<T>将命令直接执行,第一次执行后的结果,保存到内存,不会拼接命令树
    3.IQueryable<T>将语句拼接成一个命令树,当用到的时候,再执行
    4.两种操作都只访问了一次数据库

    4.为什么要有延迟加载

    a.无法确定 本次查询条件 是否 已经添加结束

    DbQuery<Student> s1 = dbContext.Students.Where(s => s.Id == 1).Where(s => s.Age > 0) as DbQuery<Student>;

    每次添加 查询条件的时候,都只是返回 包含所有添加条件的 DbQuery对象,只有最后使用的时候,才根据条件生成相应的sql语句

    b.对于外键实体,按需加载

    本次需要用到的两张 具有 主外键关系的两张表如下

    image

    image

    var tea = dbContext.Teachers.Where(t => t.tId == 1);
    //生成sql语句,如图 代码一
    Teacher teacher = tea.FirstOrDefault();
    //生成sql语句,如图 代码二
    string className = teacher.TeachClass.cName;

    代码一,如下图:

    SELECT TOP (1) 
        [Extent1].[tId] AS [tId], 
        [Extent1].[tName] AS [tName], 
        [Extent1].[tAge] AS [tAge], 
        [Extent1].[tClass] AS [tClass]
        FROM [dbo].[Teacher] AS [Extent1]
        WHERE 1 = [Extent1].[tId]

    代码二,如下图:

    exec sp_executesql N'SELECT 
        [Extent1].[cId] AS [cId], 
        [Extent1].[cName] AS [cName]
        FROM [dbo].[TeachClass] AS [Extent1]
        WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

    观察,得出结论:

    1.按需加载:在第一次执行的时候,因为没有用到外键属性,所以生成sql语句的时候,不会去去查询 TeachClass表
    2.当EF需要用到外键属性的时候,才会去加载 相应的表

    c.按需加载的缺点:

    实例代码如下:

    DbQuery<Teacher> teachers = dbContext.Teachers;
    StringBuilder sbTeacher=new StringBuilder(100);
    foreach (Teacher tea in teachers)
    {
        //每次调用 外键表Teachers上 的 外键实体时,都会去查询数据库
        //EF有个优化,相同的外键实体只查一次,即TeachClass相同只查一次
        sbTeacher.Append(tea.TeachClass.cName);
    }

    生成的SQL脚本如下:

    exec sp_executesql N'SELECT 
        [Extent1].[cId] AS [cId], 
        [Extent1].[cName] AS [cName]
        FROM [dbo].[TeachClass] AS [Extent1]
        WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

    第二次,和第三次,因为TeachClass的值相同,则只查询了一次

    exec sp_executesql N'SELECT 
        [Extent1].[cId] AS [cId], 
        [Extent1].[cName] AS [cName]
        FROM [dbo].[TeachClass] AS [Extent1]
        WHERE [Extent1].[cId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2

    5.连接查询

    既然EF是只有用到外键实体的时候,才加载相应表,那么如果我们要连接两张表要怎么做

    a.通过Include方法

    DbQuery<Teacher> teachers = dbContext.Teachers.Include("TeachClass");
    StringBuilder sbTeacher=new StringBuilder(100);
    foreach (Teacher tea in teachers)
    {
        //只有第一次 查询的使用,将数据查询 并保存到内存中,
        //接下来的操作只是在内存中读取,并没有读取数据库
        sbTeacher.Append(tea.TeachClass.cName);
    }

    查看sql语句,发现 EF为我们生成 left outer  join ,连接了两张表

    SELECT 
        [Extent1].[tId] AS [tId], 
        [Extent1].[tName] AS [tName], 
        [Extent1].[tAge] AS [tAge], 
        [Extent1].[tClass] AS [tClass], 
        [Extent2].[cId] AS [cId], 
        [Extent2].[cName] AS [cName]
        FROM  [dbo].[Teacher] AS [Extent1]
        LEFT OUTER JOIN [dbo].[TeachClass] AS [Extent2] ON [Extent1].[tClass] = [Extent2].[cId]

    b.生成 join 的另一种方式

    var teachers = dbContext.Teachers.Select(t => new {tName = t.tName, ClassName = t.TeachClass.cName}).ToList();

    生成的sql语句如下

    SELECT 
        1 AS [C1], 
        [Extent1].[tName] AS [tName], 
        [Extent2].[cName] AS [cName]
        FROM  [dbo].[Teacher] AS [Extent1]
        LEFT OUTER JOIN [dbo].[TeachClass] AS [Extent2] ON [Extent1].[tClass] = [Extent2].[cId]
    更多精彩内容请看:http://www.cnblogs.com/2star
  • 相关阅读:
    使用servletContext和类加载器加载文件
    servletConfig和servletContext的应用
    java中的正则表达式(一)
    servlet的生命周期
    servlet的基本原理
    java中类加载机制
    java中的动态代理(三)
    Navicat Premium 连接Oracle 数据库
    使用SqlServer2005 Service Broker 和 SqlDependency类提供数据更改的通知
    WebService简单使用
  • 原文地址:https://www.cnblogs.com/kimisme/p/4448597.html
Copyright © 2020-2023  润新知