讨论过后而引发对EF 6.x和EF Core查询缓存的思考

摘要:
EF6.x和EFCore中的查询缓存想必大家都有耳闻或者了解,从数据库中查询出来的实体会形成快照在内存中Copy一份且被上下文跟踪,接下来我们要讲的内容就是这个,我们来看看。EF6.x和EFCore查询缓存思考首先我利用EFCore通过一个例子来进入今天的主题,在此过程中您也可多一点思考空间并对照和您想象中的结果是否有出入或者不一致的地方。在EF6.x和EFCore中通过Find方法基于主键查询查询可重用,什么意思呢?

前言

最近将RabbitMQ正式封装引入到.NET Core 2.0项目当中,之前从未接触过这个高大上的东东跟着老大学习中,其中收获不少,本打算再看看RabbitMQ有时间写写,回来后和何镇汐大哥探讨了一点关于EF和EF Core的内容,于是乎本文就出来了。EF 6.x和EF Core中的查询缓存想必大家都有耳闻或者了解,从数据库中查询出来的实体会形成快照在内存中Copy一份且被上下文跟踪,接下来我们要讲的内容就是这个,我们来看看。

EF 6.x和EF Core查询缓存思考

首先我利用EF Core通过一个例子来进入今天的主题,在此过程中您也可多一点思考空间并对照和您想象中的结果是否有出入或者不一致的地方。

            var context = new EFCoreDbContext();
            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";   
            var blog2 = context.Blogs.FirstOrDefault(d => d.Id == 3);
            Console.WriteLine($"查询数据库后的值为{blog2.Name}"

上述我们通过Find方法查询出Blog1,然后对Name进行赋值,紧接着我们再次查询主键等于3的实体,您猜想一下blog2中的Name会等于多少呢?

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第1张

我们看看如上图所示结果为Jeffcky,有的童鞋就问了,我们进行第一次查询时值为3,然后我们仅仅只是赋值为Jeffcky并未提交,当我们再次查询相同实体时结果却为Jeffcky呢?难道不应该是3么,对吧。稍微对EF或者EF Core有所了解的大佬们明白第一次查询时会有快照,当下次查询时EF或者EF Core会在内存中根据第一次查询所对应的哈希值和主键去查找,此时查找到则直接利用内存中的对象,所以此时blog2的Name为Jeffcky。问题是不是到此就结束了呢?如果是这样,那我大半夜还浪费这时间写这篇博客!接下来我们再来看看两次查询所生成的SQL如何?

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第2张

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第3张

在EF 6.x和EF Core中 通过Find方法基于主键查询查询可重用,什么意思呢?如上第一个查询采用参数化查询,也就说如果我们下次再利用Find方法查询那么将不会到数据库查询,而是直接从内存中返回,有的童鞋就想了,恩,挺好,这样显著提高了查询性能,我只能说不一定看对应场景,如果对于非常频繁的查询我个人觉得不建议用此方法,因为还有对实体的更新操作啊,此时数据更新了却在内存中的值没有更新显示到UI上也就是说是过期了值,那么您觉得这个时候用Find方法还可取吗。对于第二个查询则直接采取赋值的形式(在我即将出版的《你必须掌握的EntityFramework 6.x和Core 2.0》书中有讲解EF 6.x查询的很大问题)这都不是事,问题是第二次我们利用FirstOrDefault方法查询此时居然走数据库了,不信,您可以看看如下利用SQL Profiler监控得到的SQL语句。

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第4张

这就让人有点费解了,第二个查询既然是到数据库中查询那为何我们得到的值当前第一次查询出来修改但未被提交的值呢?这不是自相矛盾么,EF Core这样设计的意义何在,我也想不通,我能想到的是在同一上下文中大部分情况下不会对同一实体查询多次,但是谁能保证呢。 在EF Core中除了Find会进行翻译缓存,其他比如First、FirstOrDefault、Last、LastOrDefault都会到数据库中查询,当我们利用Last或者LastOrDefault查询时会出现更有意思的事情,我们看看生成的SQL语句:

var blog3 = context.Blogs.Last();

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第5张

EF Core大哥哥们这么简单的查询问题都没测试到么,你翻译给我让我误以为这是返回所有列表了,结果一看查询出来的值却是对的,我想这是EF Core大哥哥们忘记加上TOP 1和ORDER BY了,到github上一搜索原来有大神提过这个ISSUE(https://github.com/aspnet/EntityFrameworkCore/issues/10493)在2.1版本发布会解决这个问题。好了我们回到主题所遇到的问题,在同一上下文查询同一实体,第一次查询如果我们利用Find方法查询会进行翻译缓存,待下一次再次查询时不会到数据库中查询,如果是下一次查询不是利用Find方法查询,比如FirstOrDefault此时会到数据库中查询但是此时的值却不是数据库中的值而是当前被修改而未被提交的值,那么我们如何获取数据库中的值而不是当前修改而未被提交的值呢?请往下看。

EF和EF Core获取数据库值而不是当前修改未被提交的值

我们讨论了问题的出现,接下来我们尝试利用方法来解决,我们可以对上下文所跟踪的实体进行移除通过Local.Remove,如下:

          var context = new EFCoreDbContext();

            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";
            context.Blogs.Local.Remove(blog1);
            var blog2 = context.Blogs.FirstOrDefault(d => d.Id == 3);
            Console.WriteLine($"查询数据库后的值为{blog2.Name}");

Local方法意味获取在本地所添加、修改等的实体,我们获取被上下文所本地被上下文所跟踪的实体然后移除,继而再进行查询是不是就可以移除呢?不好意思移除不了。

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第6张

利用AsNoTracking方法

这个算是最简单的方法之一了,仅仅对于查询而言通过此方法不会形成快照从而提高查询性能。

            var context = new EFCoreDbContext();

            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";
            var blog2 = context.Blogs.AsNoTracking().FirstOrDefault(d => d.Id == 3);
            Console.WriteLine($"查询数据库后的值为{blog2.Name}");

讨论过后而引发对EF 6.x和EF Core查询缓存的思考第7张

将实体状态标记为Detached不被上下文跟踪

          var context = new EFCoreDbContext();

            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";
            context.Entry(blog1).State = EntityState.Detached;
            var blog2 = context.Blogs.FirstOrDefault(d => d.Id == 3);
            Console.WriteLine($"查询数据库后的值为{blog2.Name}");

通过Reload方法刷新实体

            var context = new EFCoreDbContext();

            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";
            context.Entry(blog1).Reload();
            var blog2 = context.Blogs.FirstOrDefault(d => d.Id == 3);
            Console.WriteLine($"查询数据库后的值为{blog2.Name}");

通过GetDatabaseValues方法直接获取数据库中值

            var context = new EFCoreDbContext();

            var blog1 = context.Blogs.Find(3);
            Console.WriteLine($"数据库原始值为{blog1.Name}");
            blog1.Name = "Jeffcky";
            var blog2 = (Blog)context.Entry(blog1).GetDatabaseValues().ToObject();
            Console.WriteLine(blog2.Name);
            //或者
            var db = context.Entry(blog1).GetDatabaseValues();
            Console.WriteLine(db["Name"]);

总结

好了今天的内容就到此为止了,无论是EF 6.x还是EF Core,只要我们对一些原理足够了解才不至于出现让人意想不到的问题。希望本文对您有所帮助,下节开始讲讲RabbitMQ,我们下节再会。

免责声明:文章转载自《讨论过后而引发对EF 6.x和EF Core查询缓存的思考》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇stm32之存储系统Django 框架入门篇(安装与创建项目)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Asp.net 面向接口可扩展框架之数据处理模块及EntityFramework扩展和Dapper扩展(含干货)

接口数据处理模块是什么意思呢?实际上很简单,就是使用面向接口的思想和方式来做数据处理。 还提到EntityFramework和Dapper,EntityFramework和Dapper是.net环境下推崇最高的两种ORM工具。 1、EntityFramework是出自微软根正苗红的.net下的ORM工具,直接在Vs工具和Mvc框架中集成了,默认生成的项目就...

【.NET-EF】Entity Framework 学习笔记3:查询(模糊、排序、分页、多种条件)

前言(没废话的) 用查询时我有几个疑虑,搞得我都想用回ADO.NET来代替EF的查询,毕竟EF也就是帮你写好ADO.NET的操作框架。但还是先学了它再说吧。 1.听说EF5.0以上的性能有所提高,具体怎样还没试过。 2.EF要数据全部查出来再做分页,筛选,排序吗?  答:不用的,用linq是IQueryable类型,实现“延期执行”,意思就是可以把条件都加...

EF core的原生SQL查询以及用EF core进行分页查询遇到的问题

在用.net core进行数据库访问,需要处理一些比较复杂的查询,就不得不用原生的SQL查询了,然而EF Core 和EF6 的原生sql查询存在很大的差异。 在EF6中我们用SqlQuery和ExecuteSqlCommand进行sql语句的执行,而在EF Core中我们则使用FromSql和ExecuteSqlCommand 一.ExecuteSqlC...

什么时候用Model,什么时候用Entity?[转载知乎-备忘]

在建立一个实体类的时候,究竟是用Model还是用Entity?比如MVC中,Model存了数据实体,但是他被称为Model,而在EF中,Entity也是存放数据实体,却被称作Entity,这两者有何区别?那究竟什么时候应该用Model什么时候应该用Entity呢?  赵劼: 一般这种称谓都是根据上下文来的,例如Model是因为有MVC,或MVVM的场景下...

asp.net core系列 30 EF管理数据库架构--必备知识 迁移

一.管理数据库架构概述          EF Core 提供两种主要方法来保持 EF Core 模型和数据库架构同步。一是以 EF Core 模型为基准,二是以数据库为基准。            (1)如果希望以 EF Core 模型为准,请使用迁移。 对 EF Core 模型进行更改时,此方法会以增量方式将相应架构更改应用到数据库,以使数据库保持与...

Entity Framework 数据库先行、模型先行、代码先行

数据库先行(Database First):基于已存在的数据库,利用某些工具(如Vs提供的EF设计器)创建实体类,数据库对象与实体类的匹配关系等,你也可以手动修改这些自动生成的代码及匹配文件。 模型先行(Model First):先利用某些工具(如VS的EF设计器)设计出实体数据模型及他们之间的关系,然后再根据这些实体、关系去生成数据库对象及相关代码文件。...