EntityFramework Core 2.0 Explicitly Compiled Query(显式编译查询)
前言
EntityFramework Core 2.0引入了显式编译查询,在查询数据时预先编译好LINQ查询便于在请求数据时能够立即响应。显式编译查询提供了高可用场景,通过使用显式编译的查询可以提高查询性能。EF Core已经使用查询表达式的散列来表示自动编译和缓存查询,当我们的代码需要重用以前执行的查询时,EF Core将使用哈希查找并从缓存中返回已编译的查询。我们更希望直接使用编译查询绕过散列计算和高速缓存查找。
EntityFramework Core 2.0显式编译查询
比如我们要从博客实体中通过主键查询博客同时饥饿加载发表文章的集合列表,如下:
var id = 1; using (var context = new EFCoreDbContext()) { var blog = context.Blogs .AsNoTracking() .Include(c => c.Posts) .Where(c => c.Id == id) .FirstOrDefault(); }
当进行上述查询时,此时要经过编译翻译阶段最终返回实际结果,比如在Web网站上这样的请求很频繁,此时将严重影响响应速度导致页面加载数据过慢。从Web程序应用角度来看我们大可利用ASP.NET Core中的响应式缓存,在实际应用中我们会将查询封装为方法来使用,我们无法优化结果和查询方式,但是我们能够通过编译查询来提前保存好数据以达到缓存的效果。通过EF静态类中的扩展方法CompileQuery来实现。如下:
static async Task<Blog> GetBlogAsync(EFCoreDbContext context, int id) { Func<EFCoreDbContext, int, Task<Blog>> blog = EF.CompileAsyncQuery((EFCoreDbContext context, int Id) => context.Blogs.Include(c => c.Posts) .Where(c => c.Id == Id) .FirstOrDefault()); return await blog(context, id); }
常规查询和显式编译查询性能比较
接下来我们测试常规查询和使用显式编译查询的性能,我们利用EF Core提供的内存数据库来测试避免使用SQL Server数据库,利用SQL Server数据库很难去比较二者性能问题,因为数据库会进行查询计划优化和缓存,利用内存数据库只知道当前执行的查询不会进行任何优化, 首先我们下载EF Core内存数据库。额外再说明一点内存数据库在进行单元测试时很有意义。
接下来我们首先测试常规查询,我们预先在内存数据库中创建50条记录,然后查询十万次数据,这样来看每一次查询都会再次重新编译。
public static void Main(string[] args) { var options = new DbContextOptionsBuilder<EFCoreDbContext>() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new EFCoreDbContext(options); var stopWatch = new Stopwatch(); FillBlogs(context); stopWatch.Start(); for (var i = 0; i < 1000000; i++) { GetUnCompileQueryBlog(context); } stopWatch.Stop(); Console.Write("Compiling time:"); Console.WriteLine(stopWatch.Elapsed); Console.ReadKey(); } static void FillBlogs(EFCoreDbContext context) { for (var i = 0; i < 50; i++) { context.Blogs.Add(new Blog { Name = "Jeffcky", CreatedTime = DateTime.Now, Url = "http://www.cnblogs/com/CreateMyself", ModifiedTime = DateTime.Now, Posts = new List<Post>() { new Post() { CommentCount = i, CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now, Name = "EF Core" } } }); } context.SaveChanges(true); } static Blog GetUnCompileQueryBlog(EFCoreDbContext context) { return context.Blogs.Include(c => c.Posts) .OrderBy(o => o.Id) .FirstOrDefault(); }
我们看到上述利用常规查询总耗时27秒,接下来我们再来看看显式编译查询耗时情况。
private static Func<EFCoreDbContext, Blog> _getCompiledBlog = EF.CompileQuery((EFCoreDbContext context) => context.Blogs.Include(c => c.Posts) .OrderBy(o => o.Id) .FirstOrDefault());
var options = new DbContextOptionsBuilder<EFCoreDbContext>() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new EFCoreDbContext(options); var stopWatch = new Stopwatch(); FillBlogs(context); stopWatch.Start(); for (var i = 0; i < 100000; i++) { GetCompileQueryBlog(context); } stopWatch.Stop(); Console.Write("Compiling time:"); Console.WriteLine(stopWatch.Elapsed); Console.ReadKey();
如上通过显式编译查询耗时16秒,那么是不是就说明显式编译查询性能一定优于常规查询呢?显然不是这样,上述只是简单的测试方法,有可能运行多次显式编译查询性能还低于常规查询,所以上述简单的测试方法并不能看出常规查询和显式编译查询之间的性能差异,当查询基数足够大时则能通过机器明显看出二者之间的性能差异,这也就说明了为什么EntityFramework Core官方文档说明显式编译查询的高可用。但是显式编译查询还有且缺点,当我们进行如下查询呢?
public static void Main(string[] args) { var options = new DbContextOptionsBuilder<EFCoreDbContext>() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new EFCoreDbContext(options); var blogs = GetCompileQueryBlogs(context); Console.ReadKey(); } static Blog[] GetCompileQueryBlogs(EFCoreDbContext context) { Func<EFCoreDbContext, Blog[]> func = EF.CompileQuery((EFCoreDbContext db) => db.Blogs.Include(c => c.Posts) .OrderBy(o => o.Id) .ToArray()); return func(context); } }
当前EntityFramework Core 2.0.1版本对于显式编译查询还不支持返回IEnumerable<T>, IQueryable<T>的集合类型,期待未来能够有所支持。
总结缺陷
显式编译查询提供高可用场景,但是仍然存在其缺陷,期待未来能有更多支持,希望给阅读的您一点帮助。精简的内容,简单的讲解,希望对阅读的您有所帮助,我们明天再会。