zoukankan      html  css  js  c++  java
  • EF6学习笔记三十二:性能优化——实体缓存和翻译缓存

    要专业系统地学习EF推荐《你必须掌握的Entity Framework 6.x与Core 2.0》。这本书作者(汪鹏,Jeffcky)的博客:https://www.cnblogs.com/CreateMyself/

    这次的学习体验,从极为平常的代码中引出了两个高大上的概念——实体缓存和翻译缓存。真的觉得简单只是表面现象。

    实体缓存

     EF中提供了一个Find方法,我们来看一下这个方法的注释

    翻译结果如下:

    查找具有给定主键值的实体。如果一个实体具有给定的

    主键值存在于上下文中,然后立即返回

    向商店发出请求。否则,将向存储发出请求

    附加一个具有给定主键值的实体,如果找到该实体,则附加该实体

    返回上下文并返回。如果在上下文中或存储中没有找到实体,

    然后返回null。

    如果说该主键的实体在上下文中已经存在,那么再次查询就会直接返回,不会再向数据库提交查询。

    using (EFDbContext ctx = new EFDbContext())
    {
        ctx.Database.Log = msg => Console.WriteLine($"----------{msg}");
        var stu1 = ctx.Students.Find(1);
        var stu2 = ctx.Students.Find(1);
    
    }

    通过打印日志我们可以看到,只执行了一次查询

    但是firstOrDefault方法就不会,比如我执行两次firstOrDefault,那么EF会执行两次查询

    var stu1 = ctx.Students.FirstOrDefault(x => x.Id == 1);
    var stu2 = ctx.Students.FirstOrDefault(x => x.Id ==1);

    那么如果是这样

    var stu1 = ctx.Students.FirstOrDefault(x => x.Id == 1);
    var stu2 = ctx.Students.Find(1);

     

    可以看到也只执行了一次查询,这也符合Find的要求,当Find来查询的时候,发现该实体已经在上下文存在就直接返回了。

    那么如果我们交换一下位置,这样来。显然不符合Find的要求,会执行两次查询。

    var stu2 = ctx.Students.Find(1);
    var stu1 = ctx.Students.FirstOrDefault(x => x.Id == 1);

     这里我们发现FisrtOrDefault生成的SQL语句包含"TOP1",而Find生成的SQL语句却是“TOP2”

    这是什么问题,来看一下SingleOrDefault这个方法,这个方法如果查询不到数据或者查询到一条一上的数据就会报错,它要保证数据是唯一的,所以会有TOP2

    但是Find为什么是TOP2?Find方法时按照主键进行查询的,不可能查询到多条数据。

    看一下Find方法接受的参数,是params object[] 类型的,为什么?联合主键!但是联合主键也不可能查询出多个啊,我们来看一下

    联合主键这个真的是太冷门了,我一次都没用过,来看看怎么创建,我今天将SQL语句、DataAnnotations、Fulent API三种方式创建联合主键都Get到了

    SQL语句,先创建一张没有主键的表

    create table tb_test1
    (
    id1 int not null,
    id2 int not null,
    [name] nvarchar(10)
    )
    --  传入的两列不能是主键,不然报错,说该表已经存在主键
    alter table tb_test1 add constraint pk_id2 primary key(id1,id2)

     DataAnnotation

    public class Student3
    {
        [Key,Column(Order =1)]
        public int Id1 { get; set; }
        [Key,Column(Order = 2)]
        public int Id2 { get; set; }
        public string Name { get; set; }
    }

    Fluent API

    public class Student4
    {
        public int Id1 { get; set; }
        public int Id2 { get; set; }
        public string Name { get; set; }
    }
    modelBuilder.Entity<Student4>().ToTable("tb_Students4")
                    .HasKey(x => new { x.Id1, x.Id2 });

     DataAnnotaion的方式必须要加Column.Order,不然就会报下面这个错误。但是Fluent API方式根本就不需要配置ColumnOrder也可以。

    那这个ColumnOrder到底是什么意思。其实就是指定列的顺序,比如Id1属性的ColumnOrder为9,Name属性的ColumnOrder属性为1,那么最后生成的表结构就是,Name列在Id1列的左边,Name列先于Id1列创建。

    这个还是不错的吧,我以前也碰到过这样的情况,就是我用SQL语句给某张表添加了一列,默认是在最后面。怎么让这个列在固定的位置了,我百度了很多都没有结果。没想今天又碰到这个问题。

    肯定不是因为联合主键之间有什么主从关系才用到ColumnOrder,联合主键之间不存在主从关系,他们都是平等的。

    联合主键创建成功,可以看到表设计里面,有两把黄钥匙

    联合主键应该说的不全面,可能还有不同表之间的联合主键,这里纯粹只是想制造更多的情况来看看Find方法的执行情况。

    然后用Find查询,可以

    var stu = ctx.Students4.Find(new object[] { 1, 1 });

    如果说我只传递一个值呢?会报错,他说,有几个主键就要传递几个

    未经处理的异常:  System.ArgumentException: The number of primary key values passed must match number of primary key values defined on the entity.

     如果说我什么都不传,也是上面这个错误。那我就想到了,我可以创建一张没有主键的表,然后用Find来查询,是不是会查询到多条数据?

    但是不行,EF不能够创建没有主键的表。

    所以Find方法为什么生成的SQL包含TOP2的问题就不知道了。

    但是我们认识到实体缓存的概念,这个很有用。

    翻译缓存

     EF转换成LINQ ToEntities需要两步。第一步,将LINQ表达式树变异成数据库表达式树;第二步,将数据库表达式树生成SQL语句。

    我们要明白一点:EF总是编译LINQ表达式树到数据库表达式树并生成SQL缓存秘钥存在字典中。

    什么意思?就是LINQ表达式翻译成的SQL语句缓存起来,如果其他的执行相同那么就可以直接复用了,不用在编译成SQL了。

    来看一下下面两个简单的查询

    var stu = ctx.Students.Where(x => x.Id == 1);
    var stu2 = ctx.Students.Where(x => x.Id == 2);

    他们生成的SQL语句分别如下

    SELECT
        [Extent1].[Id] AS[Id],
        [Extent1].[Name] AS[Name],
        [Extent1].[Score] AS[Score],
        [Extent1].[AddTime]
            AS[AddTime]
    FROM[dbo].[tb_Students]
            AS[Extent1]
    WHERE 1 = [Extent1].[Id]
    SELECT
    [Extent1].[Id] AS[Id],
        [Extent1].[Name] AS[Name],
        [Extent1].[Score] AS[Score],
        [Extent1].[AddTime]
            AS[AddTime]
    FROM[dbo].[tb_Students]
            AS[Extent1]
    WHERE 2 = [Extent1].[Id]

     我们给where方法传递的是常量值,第一个查询生成的SQL语句不能被第二个复用,我们只需要将条件以变量的方式传递进去就可以翻译复用了

    int id = 1;
    int id2 = 2;
    var stu = ctx.Students.Where(x => x.Id == id);
    var stu2 = ctx.Students.Where(x => x.Id == id2);
    SELECT
        [Extent1].[Id] AS[Id],
        [Extent1].[Name] AS[Name],
        [Extent1].[Score] AS[Score],
        [Extent1].[AddTime]
            AS[AddTime]
    FROM[dbo].[tb_Students]
            AS[Extent1]
    WHERE[Extent1].[Id] = @p__linq__0

     这里只是翻译复用,并不是他会减少查询次数,这个是减轻LINQ表达式树编译成数据库表达式树的压力

    作者说:EF将缓存存在Microsoft.Extensions.Caching.Memory.MemoryCache中。

    但是,不是。这个是.NET Core里面的命名空间

    我也不知道在.NET Framework是哪个命名空间,这个或许可以查看具体是个什么情况。

    因为我有这样的疑问,EF是缓存的所有SQL翻译,还是模板?

    比如 select * from tb_students where id =1

    我说的模板是:select * from tb_students whrere id =变量

    那么翻译是在什么时候缓存的?是在运行到LINQ时,还是在执行查询时?这些疑问目前还没有解决

    传递变量的情况也有例外,比如我们在调用Skip、Take方法时,传递变量会发现生成的SQL语句里面还是常量。

    int s = 999;
    int t = 999;
    var stu = ctx.Students.OrderBy(x => x.Id).Skip(s).Take(t);
    Console.WriteLine(stu);
    SELECT
        [Extent1].[Id] AS[Id],
        [Extent1].[Name] AS[Name],
        [Extent1].[Score] AS[Score],
        [Extent1].[AddTime]
            AS[AddTime]
    FROM[dbo].[tb_Students]
            AS[Extent1]
    ORDER BY row_number() OVER(ORDER BY [Extent1].[Id] ASC)
        OFFSET 999 ROWS FETCH NEXT 999 ROWS ONLY

    对于这种情况也是有现成的解决办法的,那就是使用扩展后的SkipTake方法,这个需要引用System.Data.Entity命名空间

    int s = 999;
    int t = 999;
    var stu2 = ctx.Students.OrderBy(x => x.Id).Skip(() => s).Take(() => t);
    Console.WriteLine(stu2);
    SELECT
        [Extent1].[Id] AS [Id],
        [Extent1].[Name] AS [Name],
        [Extent1].[Score] AS [Score],
        [Extent1].[AddTime] AS [AddTime]
        FROM [dbo].[tb_Students] AS [Extent1]
        ORDER BY row_number() OVER (ORDER BY [Extent1].[Id] ASC)
        OFFSET @p__linq__0 ROWS FETCH NEXT @p__linq__1 ROWS ONLY
  • 相关阅读:
    VBA操作IE
    Eclipse中Git图标表示内容
    sqldeveloper更改语言设定
    VBA-FileToFileUpdate
    VBA-UTF-8文件的操作
    Null项目参与排序
    阿里云的学生机如何开放全部端口
    .net core3.1 webapi + vue + element-ui upload组件实现文件上传
    .net core控制台使用log4net
    vue2.x中使用三元表达式绑定class的时候遇到的坑
  • 原文地址:https://www.cnblogs.com/jinshan-go/p/10424682.html
Copyright © 2011-2022 走看看