• Linq、延迟加载、直接加载


    1、集合常用扩展方法

    Where、Max、Min、OrderBy、

    Select、//投影后的IEnumerable对象可以通过,AsQueryable转换数据类型

    First、FirstOrDefault

    Single、SingleOrDefault

    Any()判断集合是否包含元素,返回值 bool,一般比 Coun()>0 效率高。 Any 还可以指

          bool b = list.Any(p => p.Age > 50);

      等 bool b =list.Where(p=>p.Age>50).Any(); 

    Distinct(),剔除完全重复数据 

    排序升序 list.OrderBy(p=>p.Age)

        降序 list.OrderByDescending(p=>p.Age)。

      指定多个排序规则OrderBy list.OrderByDescending(p=>p.Age).ThenBy(p=>p.Salary),

      也支持 ThenByDescending()。注意这些操作不会影响原始的集合数据。 

    Skip(n)跳过前 n 条数据;

    Take(n)获取最多 n 条数据,如果不足 n 条也不会报错。

    常用来分页获取数据。 list.Skip(3).Take(2)跳过前 3 条数据获取 2 条数据。 

    Except(items1)排除当前集合中在 items1 中存在的元素 

    Union(items1)把当前集合和 items1 中组合。 

    Intersect(items1) 把当前集合和 items1 中取交集。 

    分组:

    1 IEnumerable<IGrouping<int,Person>> items=list.GroupBy(g=>g.Age);
    2 foreach(IGrouping<int,Person> item in items){
    3     Console.WriteLine("Key="+item.Key);
    4     foreach(Person p in item){
    5         Console.WriteLine(p);
    6     }
    7 }
    8 
    9 Console.ReadKey();

     

    SelectMany:把集合中每个对象的另外集合属性的值重新拼接为一个新的集合
    foreach(var s in teachers.SelectMany(t => t.Students))
    {
    Console.WriteLine(s);//每个元素都是 Person
    }
    注意不会去重,如果需要去重要自己再次调用 Distinct() 

    Join 可以实现和数据库一样的 Join 效果,对有关联关系的数据进行联合查询
    下面的语句查询所有 Id=1 的狗,并且查询狗的主人的姓名。
    var result = dogs.Where(d => d.Id > 1).Join(masters, d => d.MasterId, m => m.Id,
    (d,m)=>new {DogName=d.Name,MasterName=m.Name}); 

    Linq表达式原理:

    在写Linq表达式并完成IQueryable<>对象创建的初始化过程:声明元素类型,建立Expression对象,把Linq表达式进行解析,拆分,解析成一个表达式树,也就是对Lambda表达式的解析过程,

    Expression对象以表达式树结构存储解析结果。最后根据LINQ表达式类型给IQueryableProvider属性赋值。在需要读取数据的时候Provider属性就去解析Expression表达式树执行查询返回。

    2、延迟加载 

    解析:被延迟加载的类或者集合大多都被virtual修饰;被修饰后,可以使用当前类的导航属性。eg

    public class Students{
    
     
    
    public int id{get;set;}
    
     
    
    public string name{get;set;}
    
     
    
    public virtual Class class{get;set;}
    
     
    
    }
    Students
    using(MyContext ctx=new MyContext())
    {   
    var s=ctx.Students.First();   Console.WriteLine(s.Name)   //为什么能够.出来Class就是因为在Students时对class修饰了virtual   //如果删掉virtual则会报未将对象引用对象的实例   var c=s.Class;   Console.WriteLine(c.Name) }

    在不配置virtual时追踪查看linq编译的sql语句是

    如果去掉virtual执行的结果是只查询了Students表并且程序到c.Name时报错

     

    不去掉virtual则会把关联属性Class表也查一次,在程序执行到c.Name时执行的

    这叫“延迟加载”,只有用到关联的对象的数据,才会再去执行语句。

    注意延迟加载只在关联对象属性上,普通属性没这个东西。
    注意:启用延迟加载需要配置如下两个属性(默认就是 true,因此不需要去配置,只要别手贱设置为false 即可)
    context.Configuration.ProxyCreationEnabled = true;
    context.Configuration.LazyLoadingEnabled = true; 

    using(MyContext ctx=new MyContext()){
        var s=ctx.Students.First();
        //被virtual修饰时
        //通过反射知道s指向的对象并不是Student对象,而是s的父类才是Student对象
        //所以可以写成Student s=ctx.Students.First();
        //父类类型的变量可以指向子类类型的对象
        Console.WriteLine(s.GetType().BaseType());
        //去掉virtual时
        //可以看到s直接指向了student对象
        Console.WriteLine(s.Name)
        var c=s.ClassName;
        Console.WriteLine(c.Name)
    }

    那么,为什么呢?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 

    优点 用到的时候才加载,没用到的时候才加载,因此避免了一次性
    加载所有数据,提高了加载的速度。

    缺点:如果不用延迟加载,就可以一次数据库查询就可以把所有数据都取出来(使用 join 实现),用了延迟加载就要多次执行数据库操作,提高了数据库服务器的压力。

    因此:如果关联的属性几乎都要读取到, 那么就不要用延迟加载; 如果关联的属性只有较小的概率(比如年龄大于 7 岁的学生显示班级名字,否则就不显示)则可以启用延迟加载。

    这个概率到底是多少是没有一个固定的值,和数据、 业务、技术架构的特点都有关系,这是需要经验和直觉,也需要
    测试和平衡的。
    注意:启用延迟加载的时候拿到的对象是动态生成类的对象,是不可序列化的,因此不能直接放
    到进程外 Session Redis 等中,要转换成 DTO(后面讲)再保存。 

    如遇上述直接加载的情况,

    可以将类中引用其他类的属性声明过来不要使用类

    public class StudentModel{
        public int id{get;set;}
        public string name{get;set;}
        public string className{get;set;}
        
    }
    StudentModel

    3、直接加载

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

    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(); 

    4、延迟加载的坑

    DbContext 销毁后就不能再延迟加载,因为数据库连接已经断开
    下面的代码最后一行会报错:

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

    解决方法:

    1 Include,不延迟加载(推荐)

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


    2 关闭前把要用到的数据取出来

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

    多个取数据操作占用同一数据源

    已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。

    foreach(var s in ctx.Students)
    {
        Console.WriteLine(s.Name);
        Console.WriteLine(s.Class.Name);
    }

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

    解决方式:
    1 执行一下 ToList(),因为 ToList()就遍历然后生成 List:

    foreach(var s in ctx.Students.ToList())
    {
        Console.WriteLine(s.Name);
        Console.WriteLine(s.Class.Name);
    } 

    2)推荐做法 Include 预先加载:

    foreach(var s in ctx.Students.Include(e=>e.Class))
    {
        Console.WriteLine(s.Name);
        Console.WriteLine(s.Class.Name);
    } 

    5、IQueryable接口的操作

    interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable

    IQueryable类型的返回值的数据在函数生成sql语句时做处理

    IEnumerable类型的返回值的函数生成的sql语句后,在内存中处理

    IEnumerable在内存中执行操作性能很低

    下面的代码会报错:

    using (MyDbContext ctx = new MyDbContext())
    {
        BaseDAO<Student> dao = new BaseDAO<Student>(ctx);
        foreach(var s in dao.GetAll()){
            Console.WriteLine(s.Name);
            Console.WriteLine(s.Class.Name);
        }
    }

    原因是什么?怎么 Include

    需要 using System.Data.Entity;

    using (MyDbContext ctx = new MyDbContext())
    {
        BaseDAO<Student> dao = new BaseDAO<Student>(ctx);
        foreach(var s in dao.GetAll().Include(t=>t.Class))
        {
            Console.WriteLine(s.Name);
            Console.WriteLine(s.Class.Name);
        }
    } 

    有两个版本的 Include AsNoTracking
    1) DbQuery 中的: DbQuery<TResult> AsNoTracking() DbQuery<TResult> Include(string path)
    2) QueryableExtensions AsNoTracking<T>(this IQueryable<T> source) 、
    Include<T>(this IQueryable<T> source, string path) Include<T, TProperty>(this IQueryable<T>
    source, Expression<Func<T, TProperty>> path)
    DbSet 继承自 DbQuery Where() Order Skip()等这些方法返回的是 IQueryable 接口。
    IQueryable Include AsNoTracking using System.Data.Entity 

     

     

  • 相关阅读:
    asp.net mvc 两级分类联动方法示例
    动手实践虚拟网络
    KVM 网络虚拟化基础
    LVM 类型的 Storage Pool
    KVM 存储虚拟化
    CPU 和内存虚拟化原理
    远程管理 KVM 虚机
    启动第一个 KVM 虚机
    准备 KVM 实验环境
    虚拟化
  • 原文地址:https://www.cnblogs.com/cuijl/p/6622428.html
Copyright © 2020-2023  润新知