实体框架核心3.1.5:如何从linq查询中获取表收集逻辑以避免客户端评估错误?

时间:2020-07-17 17:47:43

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

我有这样的查询

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 = ..”?

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

1 个答案:

答案 0 :(得分:0)

回答您的问题:

  • 不会将整个Contacts表都加载到内存中。
  • 这比使用数据库查询要慢,但是除非您有大量记录,否则您将无法“人工”测量它(不进行压力测试会指出,这可能比使用数据库查询慢150ms) 10000条记录。)

这是为什么:

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

您可以进行一些更改以使其更快:

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

如果您将查询更改为Linq查询,我们将很高兴为您提供帮助并对其进行优化。