简体   繁体   English

Entity Framework Core 3.1.5:如何在 linq 查询之外提取表集合逻辑以避免客户端评估错误?

[英]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?

I've a query like this我有这样的查询

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;
}

This query was working fine.该查询运行良好。 But when we upgraded to .NET Core 3.1.5 and Entity Framework Core 3.1.5, it started throwing a client-side evaluation error:但是当我们升级到 .NET Core 3.1.5 和 Entity Framework Core 3.1.5 时,它开始抛出客户端评估错误:

"could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()." “无法翻译。以可以翻译的形式重写查询,或者通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用来显式切换到客户端评估。”

So I had to take the following line out from the query:所以我不得不从查询中删除以下行:

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

So re-wrote the query like this:所以重新编写了这样的查询:

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;
}

Here这里

con.Contacts

is a table collection in Contract.cs classContract.cs中的表集合 class

I've added a property like this in ContractInquiry.cs class:我在ContractInquiry.cs class 中添加了这样的属性:

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

This is working fine as well.这也很好用。

Question: Doing like this will work fine but at run time, the table collection "con.Contacts" will be in memory right?问题:这样做会很好,但在运行时,表集合“con.Contacts”将在 memory 中,对吗? And that will impact the performance of the query right if the table is a huge collection?如果表是一个巨大的集合,这会影响查询的性能吗? So is there a work around for this instead of using a memory table?那么有没有解决这个问题而不是使用 memory 表的方法? How can I take out the "ServiceManager =.." from the select clause in my first query?如何在我的第一个查询中从 select 子句中取出“ServiceManager =..”?

UPDATE: Can someone answer my question?更新:有人可以回答我的问题吗?

To answer your question:回答你的问题:

  • No the whole Contacts table won't be loaded into memory.不,整个联系人表不会加载到 memory 中。
  • It will be slower than using a database query but unless you have a crazy amount of records you won't be able to 'humanly' measure it (obv. a stress test will point out that this may be slower by 150ms on 10000 records).它会比使用数据库查询慢,但除非你有大量的记录,否则你将无法“人为地”测量它(obv。压力测试会指出这可能会在 10000 条记录上慢 150 毫秒) .

Why this is:这是为什么:

EF Core only loads related data and when it is needed. EF Core 仅在需要时加载相关数据。 For example you have 1000 of these ContractInquiry records when calling .ToList() .例如,您在调用.ToList()时有 1000 条这样的 ContractInquiry 记录。 Every one of these records contain ten contacts.这些记录中的每一个都包含十个联系人。 Then EF Core will load only 1000*10 contacts.然后 EF Core 将只加载 1000*10 个联系人。 Due to references if any of these overlap they will share memory location and only a reference to it will be saved.由于引用,如果其中任何一个重叠,它们将共享 memory 位置,并且只会保存对其的引用。

Some changes you can do to make this even faster:您可以进行一些更改以使其更快:

  • Change .ToList() to .AsEnumerable() ..ToList()更改为.AsEnumerable() You can do this because you only iterate over that list once, so you save a whole iteration using .AsEnumerable() .您可以这样做,因为您只迭代该列表一次,因此您可以使用.AsEnumerable()保存整个迭代。 (to create a list the program must iterate over it and then you iterate over it again). (要创建一个列表,程序必须对其进行迭代,然后再次对其进行迭代)。 Also you are returning an IEnumerable so creating a list is pointless (if you are iterating over it once, which is the case here) unless you later cast it back, which I do not recommend.此外,您要返回一个 IEnumerable,因此创建一个列表是没有意义的(如果您对其进行一次迭代,这里就是这种情况),除非您稍后将其强制转换回去,我不建议这样做。
  • Add .AsNoTracking() in the query.在查询中添加.AsNoTracking() I don't know how you can achieve the same thing with this type of querying (I only use Linq).我不知道如何使用这种类型的查询实现相同的目的(我只使用 Linq)。 This will save a lot of time because EF Core will not have to create tracking (and will also save memory).这将节省大量时间,因为 EF Core 不必创建跟踪(并且还将节省内存)。

If you would change the query to a Linq query I would be happy to have a look at it and help you optimise it.如果您将查询更改为 Linq 查询,我很乐意查看并帮助您优化它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM