【问题标题】:Entity Framework Core 3.1.5: How to take a table collection logic out side of a linq query to avoid client-side evaluation error?Entity Framework Core 3.1.5:如何从 linq 查询中取出表集合逻辑以避免客户端评估错误?
【发布时间】:2020-07-17 17:47:43
【问题描述】:

我有这样的查询

public IEnumerable<ContractInquiry> ContractInquiry(Guid itemSoldID)
{
  IEnumerable<ContractInquiry> result;
  using (var context = new ContractDbContext(_ctxOptions))
  {
    var qry = from con in context.Contracts
              join product in context.ContractProducts on con.ID equals product.ContractID
              join service in context.ServiceDetails on con.ID equals service.ContractID into tmpService
              from service in tmpService.DefaultIfEmpty()
              where product.ItemSoldID == itemSoldID
                    && product.ProductStatus != ProductStatus.Deleted.ToString()
                    && con.Status != Status.Deleted.ToString()
              select new ContractInquiry
              {
                 ServiceID = con.ID,
                 ServiceType = con.ServiceType,
                 ServiceDate = service.ServiceDate,
                 ServiceNumber = service.ServiceNumber,
                 ServiceManager = con.Contacts.Where(q => q.Role.Contains(ContractRole.ServiceManager.ToString()))
                                  .OrderBy(o => o.ID).FirstOrDefault()
              };
     result = qry.ToList();
   }
   return result;
}

此查询运行良好。但是当我们升级到 .NET Core 3.1.5 和 Entity Framework Core 3.1.5 时,它开始抛出客户端评估错误:

“无法翻译。要么以可翻译的形式重写查询,要么通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。 "

所以我不得不从查询中取出以下行:

ServiceManager = con.Contacts.Where(q => q.Role.Contains(ContractRole.ServiceManager.ToString()))
                                  .OrderBy(o => o.ID).FirstOrDefault()

所以重新编写了这样的查询:

public IEnumerable<ContractInquiry> ContractInquiry(Guid itemSoldID)
{
  List<ContractInquiry> result;
  using (var context = new ContractDbContext(_ctxOptions))
  {
    var result = (from con in context.Contracts
              join product in context.ContractProducts on con.ID equals product.ContractID
              join service in context.ServiceDetails on con.ID equals service.ContractID into tmpService
              from service in tmpService.DefaultIfEmpty()
              where product.ItemSoldID == itemSoldID
                    && product.ProductStatus != ProductStatus.Deleted.ToString()
                    && con.Status != Status.Deleted.ToString()
              select new ContractInquiry
              {
                 ServiceID = con.ID,
                 ServiceType = con.ServiceType,
                 ServiceDate = service.ServiceDate,
                 ServiceNumber = service.ServiceNumber,
                 Contacts = con.Contacts
              }).ToList();
   }
   
   result.ForEach(con => con.Contacts.Where(q => q.Role.Contains(ContractRole.ServiceManager.ToString()))
                                  .OrderBy(o => o.ID).FirstOrDefault();

   return result;
}

这里

con.Contacts

Contract.cs类中的一个表格集合

我在 ContractInquiry.cs 类中添加了这样的属性:

[JsonIgnore]
public IEnumerable<Contact> Contacts { set; get; }

这也很好。

问题: 这样做可以正常工作,但在运行时,表集合“con.Contacts”将在内存中,对吗?如果表是一个巨大的集合,那会影响查询的性能吗?那么有没有办法解决这个问题而不是使用内存表?如何在我的第一个查询中从 select 子句中取出“ServiceManager = ..”?

更新:有人可以回答我的问题吗?

【问题讨论】:

  • 与其从查询中取出,不如找出表达式的哪个部分导致了问题。 EFC 最有可能显示整个表达式,但问题出在其中一个部分(WhereOrderBy 等)。所以开始一一删除它们(我会从Where 子句开始)直到它起作用。
  • 我检查了一下,发现问题出在 select 子句 "ServiceManager = con.Contacts.Where(q => ........" 中的以下行。如果我评论这条线它有效。我希望问题出在 .Contains() 上。这就是为什么我需要取消客户端评估。
  • 你需要专注于那个表达而不是整体。例如,从显示的代码中不清楚什么是 ContractRole - 类变量?静态成员?还有ContractRole.ServiceManager属性的类型和.ToString()调用的需要。在这种情况下我会尝试的第一件事是将该部分移动到查询之外的变量,例如var contractRoleServiceManager = ContractRole.ServiceManager.ToString(); 然后在里面使用q.Role.Contains(contractRoleServiceManager)
  • ContractRole 是一个枚举。我可以试试你的建议。但我的问题是:有没有办法将“con.Contacts”集合带到外面单独评估?
  • 有人能回答我的问题吗?

标签: c# .net-core-3.1 entity-framework-core-3.1


【解决方案1】:

回答你的问题:

  • 不,整个联系人表都不会加载到内存中。
  • 它会比使用数据库查询慢,但除非你有大量的记录,否则你将无法“人工”测量它(显然,压力测试会指出这可能会慢 150 毫秒) 10000 条记录)。

为什么会这样:

EF Core 仅在需要时加载相关数据。例如,当您调用.ToList() 时,您有 1000 条这样的 ContractInquiry 记录。这些记录中的每一个都包含十个联系人。然后 EF Core 将仅加载 1000*10 个联系人。由于引用如果其中任何重叠,它们将共享内存位置,并且只会保存对它的引用。

您可以进行一些更改以加快速度:

  • .ToList() 更改为.AsEnumerable()。您可以这样做,因为您只迭代该列表一次,因此您可以使用 .AsEnumerable() 保存整个迭代。 (要创建一个列表,程序必须对其进行迭代,然后再次对其进行迭代)。此外,您正在返回一个 IEnumerable,因此创建一个列表是没有意义的(如果您迭代它一次,这里就是这种情况),除非您稍后将其转换回来,我不建议这样做。
  • 在查询中添加.AsNoTracking()。我不知道如何通过这种类型的查询来实现相同的目标(我只使用 Linq)。这将节省大量时间,因为 EF Core 不必创建跟踪(并且还可以节省内存)。

如果您将查询更改为 Linq 查询,我很乐意查看并帮助您优化它。

【讨论】:

  • 感谢 Bencel 的详细解答。让我根据你的建议来解决。
猜你喜欢
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-08
  • 1970-01-01
相关资源
最近更新 更多