先来看一下Entity Framework缓慢的初始化速度给我们更新程序带来的一种痛苦。
我们手动更新程序时通常的操作步骤如下:
1)把Web服务器从负载均衡中摘下来
2)更新程序
3)预热(发出一个请求,完成程序的初始化)
4)把完成更新的Web服务器挂上负载均衡
在预热阶段,我们一般是向首页(www.cnblogs.com)发出请求(首页的加载没有用到Entity Framework)。
如果仅这样预热后就将Web服务器上线,将会给部分用户带来糟糕的用户体验——比如,第1位在发布后推荐博文的用户将会等待7秒钟左右(推荐功能中使用了Entity Framework Code First)。
为了避免这样的糟糕体验,我们不得不在预热时发出推荐博文的请求,等EF完成初始化后再发布。这不是一个好的解决方法,因为每个定义的DbContext类型都要进行这个初始化操作。
昨天,我们找到了一个更好的缓解这个问题的方法,在这篇博文中向大家分享一下。
为什么Entity Framework的初始化速度慢如蜗牛呢?
对于在应用程序中定义的每个DbContext类型,在首次使用时,Entity Framework都会根据数据库中的信息在内存生成一个映射视图(mapping views),而这个操作非常耗时。
using (var dbcontext = new CnblogsDbContext()) { //... }
比如上面的代码,在第1次调用CnblogsDbContext进行数据库操作时会进行缓慢的mapping views生成操作,后续的CnblogsDbContext操作会共享已经生成的mapping views,不受这个问题影响。但是要注意的是你定义的每一个DbContext都会面临这个问题。
而我们的缓解之道则是在应用程序初始化时一次性触发所有的DbContext进行mapping views的生成操作——调用StorageMappingItemCollection的GenerateViews()方法。
代码如下(Entity Framework的版本至少是6.0才支持):
using (var dbcontext = new CnblogsDbContext()) { var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); } //对程序中定义的所有DbContext逐一进行这个操作
对于ASP.NET应用程序 ,可以将上面的代码放在Application_Start或者PreApplicationStartMethod中执行。
我们采用这个方法之后,在程序更新时只需发一个请求让程序启动起来,比如访问首页(www.cnblogs.com),就可以直接发布。而第1位进行推荐博文操作的用户,等待时间由原来7秒减少到0.5-0.6秒。
【参考资料】
1. 预先生成视图
通过代码的方式来预先生成视图,要求EntityFramework是6.0及以上版本。
控制台程序:
using System.Data.Entity.Infrastructure; using System.Data.Entity.Core.Mapping; using System.Data.Entity.Core.Metadata.Edm;
using (var ctx = new LibingContext()) { var objectContext = ((IObjectContextAdapter)ctx).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); //var products = ctx.Products.ToList(); }
MVC程序:
protected void Application_Start() { using (var ctx = new mcccEntities()) { var objectContext = ((IObjectContextAdapter)ctx).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); } }
2. NGen优化
参考:Entity Framework Improving Startup Performance with NGen (EF6 Onwards)
主要是针对EF6及以上版本的,因为低于这个版本的自带该特性,在6.0之前的EF中,EF的运行时核心类库也是.NET框架的一部分,其本地映像在.NET 核心类库加载时自动加载,在6.0及之后的版本,EF整个运行时已经被集成到EntityFramework NuGet包中,本地映像需要使用NGen工具来生成才能达到类似的效果。
NGen的作用以及为什么能够加快应用程序的启动性能:
.NET框架支持为托管应用或者程序集生成本地映像文件来帮助应用程序更快启动和在一些情况下减少内存占用。
在应用程序执行之前,通过将托管代码程序集翻译为包含本地机器指令的文件,能够减少.NET JIT编译器在应用程序启动的时候,生成本地指令代码这一过程,从而能够加快应用程序启动。
NGen使用方法:
(1)以管理员身份启动控制台cmd程序
(2)切换到本机.NET Framework目录下:
32位:%WINDIR%\Microsoft.NET\Framework\v4.0.30319\
64位:%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\
(3)运行ngen install + 程序集的路径和名称
C:\WINDOWS\system32>cd /d C:\Windows\Microsoft.NET\Framework64\v4.0.30319 C:\Windows\Microsoft.NET\Framework64\v4.0.30319>ngen install F:\10-Projects\Libing\Libing.App\bin\Debug\EntityFramework.dll C:\Windows\Microsoft.NET\Framework64\v4.0.30319>ngen install F:\10-Projects\Libing\Libing.App\bin\Debug\EntityFramework.SqlServer.dll