简体   繁体   English

实体框架6:虚拟集合懒加载甚至在查询中显式加载

[英]Entity Framework 6: virtual collections lazy loaded even explicitly loaded on a query

I have a problem with EF6 when trying to optimize the queries. 尝试优化查询时,EF6出现问题。 Consider this class with one collection: 考虑此类与一个集合:

public class Client
{
    ... a lot of properties
    public virtual List<Country> Countries { get; set; }
}

As you might know, with Lazy Loading I have this n+1 problem, when EF tries to get all the Countries, for each client. 如您所知,在EF尝试获取每个客户的所有国家/地区时,使用延迟加载会遇到n + 1个问题。

I tried to use Linq projections; 我尝试使用Linq投影; for example: 例如:

        return _dbContext.Clients
            .Select(client => new
            {
                client,
                client.Countries
            }).ToList().Select(data =>
            {
                data.client.Countries = data.Countries; // Here is the problem
                return data.client;
            }).ToList();

Here I'm using two selects: the first for the Linq projection, so EF can create the SQL, and the second to map the result to a Client class. 在这里,我使用了两个选择:第一个用于Linq投影,以便EF可以创建SQL,第二个将结果映射到Client类。 The reason for that is because I'm using a repository interface, which returns List<Client> . 这样做的原因是因为我使用的是存储库接口,该接口返回List<Client>

Despite the query is generated with the Countries in it, EF still is using Lazy Loading when I try to render the whole information (the same n+1 problem). 尽管查询是在其中包含国家/地区的情况下生成的,但是当我尝试呈现整个信息时,EF仍在使用延迟加载(相同的n + 1问题)。 The only way to avoid this, is to remove the virtual accessor: 避免这种情况的唯一方法是删除虚拟访问器:

public class Client
{
    ... a lot of properties
    public List<Country> Countries { get; set; } 
}

The issue I have with this solution is that we still want to have this property as virtual. 该解决方案的问题在于,我们仍然希望将此属性设置为虚拟。 This optimization is only necessary for a particular part of the application, whilst on the other sections we want to have this Lazy Loading feature. 此优化仅对于应用程序的特定部分是必需的,而在其他部分,我们希望具有此延迟加载功能。

I don't know how to "inform" EF about this property, that has been already lazy-loaded via this Linq projection. 我不知道如何将此属性“通知” EF,该属性已通过此Linq投影进行了延迟加载。 Is that possible? 那可能吗? If not, do we have any other options? 如果没有,我们还有其他选择吗? The n+1 problems makes the application to take several seconds to load like 1000 rows. n + 1问题使应用程序花费几秒钟来加载1000行。

Edit 编辑

Thanks for the responses. 感谢您的答复。 I know I can use the Include() extension to get the collections, but my problem is with some additional optimizations I need to add (I'm sorry for not posting the complete example, I thought with the Collection issue would be enough): 我知道我可以使用Include()扩展名来获取集合,但是我的问题是我需要添加一些其他优化功能(对不起,我没有发布完整的示例,我认为使用集合问题就足够了):

public class Client
{
    ... a lot of properties
    public virtual List<Country> Countries { get; set; }
    public virtual List<Action> Actions { get; set; }
    public virtual List<Investment> Investments { get; set; }
    public User LastUpdatedBy {
        get {
          if(Actions != null) {
              return Actions.Last();
          }       
        }
    }
}

If I need to render the clients, the information about the last update and the number of investments ( Count() ), with the Include() I practically need to bring all the information from the database. 如果需要渲染客户端,则需要使用Include()来获取有关最近更新和投资数量( Count() )的信息,实际上我需要从数据库中获取所有信息。 However, if I use the projection like 但是,如果我使用像

        return _dbContext.Clients
              .Select(client => new
              {
                    client,
                    client.Countries,
                    NumberOfInvestments = client.Investments.Count() // this is translated to an SQL query
                    LastUpdatedBy = client.Audits.OrderByDescending(m => m.Id).FirstOrDefault(),
              }).ToList().Select(data =>
              {
                    // here I map back the data
                    return data.client;
              }).ToList();

I can reduce the query, getting only the required information ( in the case of LastUpdatedBy I need to change the property to a getter/setter one, which is not a big issue, as its only used for this particular part of the application ). 我可以减少查询,只获取所需的信息( 在LastUpdatedBy的情况下,我需要将属性更改为getter / setter,这不是什么大问题,因为它仅用于应用程序的此特定部分 )。

If I use the Select() with this approach (projection and then mapping), the Include() section is not considered by EF. 如果我将Select()与这种方法(投影然后是映射)一起使用,则EF不考虑Include()部分。

don't know what you want to do, you are using lambda expression not linq, and your second select it's unnecessary. 不知道要做什么,您使用的是lambda表达式而不是linq,而第二个选择是不必要的。

data.client is client , data.Countries is client.Countries , so data.client.Countries = data.Countries alway true. data.clientclientdata.Countriesclient.Countries ,所以data.client.Countries = data.Countries送花儿给人真实的。

if you don't want lazy load Countries , use _dbContext.Clients.Include("Countries").Where() or select () . 如果您不希望延迟加载Countries ,请使用_dbContext.Clients.Include("Countries").Where() or select ()

In order to force eager loading of virtual properties you are supposed to use Include extension method. 为了强制渴望加载虚拟属性,您应该使用Include扩展方法。 Here is a link to MSDN https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx . 这是MSDN https://msdn.microsoft.com/zh-cn/library/jj574232(v=vs.113).aspx的链接。

So something like this should work: 所以这样的事情应该工作:

 return _dbContext.Clients.Include(c=>c.Countries).ToList();

If i understand correctly you can try this 如果我理解正确,可以尝试一下

_dbContext.LazyLoading = false;

var clientWithCountres =  _dbContext.Clients
                                    .Include(c=>c.Countries)
                                    .ToList();

This will fetch Client and only including it Countries . 这将获取Client ,只包括它Countries If you disable lazy-loading the no other collection will load from the query. 如果禁用延迟加载,则不会从查询中加载其他集合。 Unless you are specifying a include or projection. 除非您指定包含或投影。

FYI : Projection and Include() doesn't work together see this answer If you are projection it will bypass the include. 仅供参考:Projection和Include()不能一起使用,请参见以下答案:如果您正在投影,它将绕过include。 https://stackoverflow.com/a/7168225/1876572 https://stackoverflow.com/a/7168225/1876572

Im not 100% sure but I think your issue is that you are still maintaining a queryable for your inner collection through to the end of the query. 我不是100%肯定,但是我认为您的问题是,您仍将维护内部查询的可查询性,直到查询结束。

This queryable is lazy (because in the model it was lazy), and you havent done anything to explain that this should not be the case, you have simply projected that same lazy queryable into the result set. 这个可查询的是惰性的(因为在模型中它是惰性的),并且您没有做任何解释来说明情况并非如此,您只是将相同的惰性可查询项投影到结果集中。

I cant tell you off the top of my head what the right answer here is but I would try things around the following: 我无法直截了当地告诉您什么是正确的答案,但是我会尝试以下方法:

1 use a projection on the inner queriable also eg 1也使用内部可投影的投影,例如

return _dbContext.Clients
            .Select(client => new
            {
                client,
                Countries = client.Countries.Select(c=>c)// or a new Country
            })

2 Put the include at the end of the query (Im pretty sure include applies to the result not the input. It definitally doesnt work if you put it before a projection) eg: 2将include放在查询的末尾(我很确定include应用于结果而不是输入。如果将其放在投影之前,它肯定不起作用)例如:

_dbContext.Clients
            .Select(client => new
            {
                client,
                client.Countries
            }.Include(c=>c.Countries)`

3 Try specifying the enumeration inside the projection eg: 3尝试在投影内部指定枚举,例如:

_dbContext.Clients
            .Select(client => new
            {
                client,
                Countries = client.Countries.AsEnumerable() //perhaps tolist if it works
            }`

I do want to caviat this by saying that I havent tried any of the above but I think this will set you on the right path. 我想通过说我还没有尝试以上任何方法来避免这种情况,但是我认为这将使您走上正确的道路。

A note on lazy loading 关于延迟加载的注意事项

IMO there are very few good use cases for lazy loading. IMO很少有延迟加载的好用例。 It almost always causes too many queries to be generated, unless your user is following a lazy path directly on the model. 除非您的用户直接在模型上遵循惰性路径,否则它几乎总是会导致生成太多查询。 Use it only with extreme caution and IMO not at all in request response (eg web) apps. 仅在极其谨慎的情况下使用它,而在请求响应(例如Web)应用程序中则完全不使用IMO。

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

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