【问题标题】:EF Core Complex query gets cached for each variation of parametersEF Core Complex 查询为每个参数变体缓存
【发布时间】:2021-02-10 12:23:41
【问题描述】:

我有一个复杂的查询,似乎会导致我的应用程序内存出现内存泄漏。

据我了解,查询结果被缓存,因此每次执行查询时都不需要进行此处理。 https://docs.microsoft.com/en-us/ef/core/querying/how-query-works

但是看起来查询正在为每个登录到系统的用户缓存。从内存转储看来,我们为同一个查询准备了数千个已编译的查询缓存对象。

查询如下所示。

public async Task<IList<EmployeeInboxMessage>> GetEmployeeMessagesAsync(long employeeId)
        {
            return await (from message in this.Repository.Set
                          join userStep in this.Repository.Context.Set<UserWorkflowHeaderStep>() on message.UserWorkflowStepId equals userStep.UserWorkflowStepId into userSteps
                          from userStep in userSteps.DefaultIfEmpty()
                          join acceptedStep in this.Repository.Context.Set<CompanyWorkflowStep>() on userStep.AcceptedStepId equals acceptedStep.WorkflowStepId into acceptedSteps
                          from acceptedStep in acceptedSteps.DefaultIfEmpty()
                          join rejectedStep in this.Repository.Context.Set<CompanyWorkflowStep>() on userStep.RejectedStepId equals rejectedStep.WorkflowStepId into rejectedSteps
                          from rejectedStep in rejectedSteps.DefaultIfEmpty()

                          let step =
                              message.InboxEntryType == InboxEntryType.Claims ||
                              message.InboxEntryType == InboxEntryType.AdvancedLeave ||
                              message.InboxEntryType == InboxEntryType.ChangeRequest
                                  ? new WorkflowHeaderStep
                                  {
                                      WorkflowItem = userStep.WorkflowItem,
                                      AcceptedStepId = userStep.AcceptedStepId,
                                      AcceptedStep = acceptedStep == null ? null : new WorkflowStep
                                      {
                                          OnApprovalActionId = acceptedStep.OnApprovalActionId,
                                          OnRejectionAction = acceptedStep.OnRejectionAction,
                                          OrderNumber = acceptedStep.OrderNumber
                                      },
                                      RejectedStepId = userStep.RejectedStepId,
                                      RejectedStep = rejectedStep == null ? null : new WorkflowStep
                                      {
                                          OnApprovalActionId = rejectedStep.OnApprovalActionId,
                                          OnRejectionAction = rejectedStep.OnRejectionAction,
                                          OrderNumber = rejectedStep.OrderNumber
                                      }
                                  }
                                  : null

                          let employeeName = string.IsNullOrWhiteSpace(message.OBOEmployee.PreferredName)
                              ? message.OBOEmployee.FullName
                              : message.OBOEmployee.PreferredName + " " + message.OBOEmployee.LastName

                          where message.EmployeeId == employeeId
                          orderby message.EffectiveDate
                          select new EmployeeInboxMessage
                          {
                              Message = message,
                              UserStep = step,
                              UserId = message.UserId,
                              UserName = message.User.FullName,
                              EmployeeName = message.OBOEmployeeId.HasValue
                                  ? employeeName
                                  : message.User.FullName,
                              RelatedPrimaryKey =
                                  message.InboxEntryType == InboxEntryType.Claims ||
                                  message.InboxEntryType == InboxEntryType.AdvancedLeave ||
                                  message.InboxEntryType == InboxEntryType.ChangeRequest
                                      ? userStep.RelatedPrimaryKey
                                      : message.UserWorkflowStepId!.Value,
                              StartUserCompanyId = message.StartUser.CompanyId
                          }).ToListAsync();
        }

【问题讨论】:

  • 1.更新到 EF Core 3.x 或 5 2. EF Core 本身不缓存查询结果 3. 如果仍有问题,请将问题发布到 EF Core 存储库。
  • 我们有一个活动系统,很遗憾我们不能只更新到核心 3。但我们正在更新过程中。如果 E 核心没有缓存查询,它是什么?文档声明“结果被缓存,因此每次执行查询时都不需要进行此处理”
  • 仅缓存具有 PK 的实体。如果您对此有疑问 - 始终重新创建 DataContext。
  • 在 .netcore 中,您通过依赖注入注册 DBContext。至少这是默认行为。您还将它注册为作用域,这意味着在每个请求上都会创建 DBContext。所以我不需要重新创建 DBContext 作为框架处理
  • 确实如此。但我不知道你如何实例化上下文,所以这只是建议。编写简单的测试,运行此函数 100 次并检查 CompiledQuery 缓存发生了什么,如果它增长 - EF 有错误。由于一切都从 EF 2 更改为 EF 3,因此在当前版本中可能不是问题。

标签: linq .net-core entity-framework-core ef-core-2.2 dotmemory


【解决方案1】:

看起来您的查询对于 EF 创建单个 CompiledQueryCache 对象来说很复杂,并且它正在为您传入的每个“employeeId”添加一个缓存版本。看起来已经有 27804 个版本。

如果您重组查询,那么它可能会解决您的问题。尝试删除“let step = ...”和“let employeeName = ...”。最坏的情况是您需要创建原始 SQL 或视图。

没有很多文档解释 EF 如何创建查询缓存(我找不到)。除非某些 EF 创建者提供一些输入,或者您可以随时查看他们的代码,否则您可能永远不会知道您的问题到底是什么。

如果您在更改查询后仍有问题,最好使用 efcore https://github.com/dotnet/efcore 记录问题

【讨论】:

  • 所以我最终删除了“让”查询,并将查询从手动连接更改为使用导航属性。通过简化查询,它修复了我的内存泄漏。复杂的查询会导致客户端评估,我怀疑这是内存使用率高的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-29
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多