首页 > 解决方案 > 第一次调用后,对不同数据库上下文的 EF 查询似乎会缓存

问题描述

我有一个包含三个表的数据库,我正在制作一个程序,该程序将选择并需要同时显示所有三个表的结果。

为了加快记录的获取速度,我决定使请求异步,以便它们可以同时发生。
由于您不能对同一个 DB 上下文执行多个并发操作,因此我总共只实例化三个上下文,并在每个上下文上启动一个 Linq 查询“ToListAsync()”。

但是发生了一些奇怪的事情(对我来说)——异步版本并不比同步版本快。无论我是使用“.ToListAsync()”异步获得结果还是使用“.ToList()”同步获得结果,它们都需要相同的时间。使用同步版本,第一个操作大约需要 2 秒才能完成,而第二个大约需要 170 毫秒,第三个大约需要 90 毫秒。我想某种执行计划或缓存或建立连接是什么使第一个请求需要很多时间,而其他请求几乎立即完成,但我想知道为什么会发生这种情况以及是否有办法做到这一点快点。
忘了提到异步版本需要相同的时间 - 2s。

相关代码:

        int maxRecords = 10;
        var resultsTableOne = dbContext.tableOne.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();
        var resultsTableTwo = dbContext.tableTwo.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();
        var resultsTableThree = dbContext.tableThree.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();

此处简化了选择条件。对于异步版本,“ToList”被替换为“ToListAsync”,它返回一个任务,在此处显示的代码之后调用“GetAwaiter().GetResult()”(并且“dbContext”被替换为三个单独的实例)。

PS
同步版本的结果与一个和三个 DbContext 相同。即,所有三个操作是在同一个实例上执行还是在其自己的实例上执行都需要相同的时间。

标签: c#databaseentity-frameworkasynchronous

解决方案


为了加快记录的获取速度,我决定使请求异步,以便它们可以同时发生。

这不是async/await模式的作用。您似乎想要做的是并行化,您可以使用async方法及其关联的方法Task,但这需要 3 个不同的 DbContext 实例。作为与您拥有的代码相关的一个非常简单的示例:

using (var context1 = new AppDbContext())
{
    using (var context2 = new AppDbContext())
    {
        using (var context3 = new AppDbContext())
        {
            var table1Task = context1.tableOne
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
            var table2Task = context2.tableTwo
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
            var table3Task = context3.tableThree
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
           var resultTask = Task.WhenAll(table1Task, table2Task, table3Task);
           try
           {
               resultTask.Wait();
               var result1 = table1Task.Result;
               var result2 = table2Task.Result;
               var result3 = table3Task.Result;
               // Use your 3 lists.
           }
           catch(Exception ex)
           {// Handle any exception when running.
           }
        }
    }
}

每个查询都必须在单独的 DbContext 上运行,因为 DbContext 不是线程安全的。

正如评论中所提到的,为了提高性能,包括索引和使用投影而不是返回整个实体,还有很多更好的方法需要研究。如果 Table1 的行非常大,其中包含许多结果代码不需要的大字段,则使用投影Select来仅获取您需要的列可以帮助提供更高性能的代码。并行性只是我在面对运行多个查询时会考虑的事情,其中​​一个或多个查询可能需要大量时间,并且每个查询的结果是完全独立的。


推荐阅读