[英]Entity Framework 6: virtual collections lazy loaded even explicitly loaded on a query
尝试优化查询时,EF6出现问题。 考虑此类与一个集合:
public class Client
{
... a lot of properties
public virtual List<Country> Countries { get; set; }
}
如您所知,在EF尝试获取每个客户的所有国家/地区时,使用延迟加载会遇到n + 1个问题。
我尝试使用Linq投影; 例如:
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();
在这里,我使用了两个选择:第一个用于Linq投影,以便EF可以创建SQL,第二个将结果映射到Client类。 这样做的原因是因为我使用的是存储库接口,该接口返回List<Client>
。
尽管查询是在其中包含国家/地区的情况下生成的,但是当我尝试呈现整个信息时,EF仍在使用延迟加载(相同的n + 1问题)。 避免这种情况的唯一方法是删除虚拟访问器:
public class Client
{
... a lot of properties
public List<Country> Countries { get; set; }
}
该解决方案的问题在于,我们仍然希望将此属性设置为虚拟。 此优化仅对于应用程序的特定部分是必需的,而在其他部分,我们希望具有此延迟加载功能。
我不知道如何将此属性“通知” EF,该属性已通过此Linq投影进行了延迟加载。 那可能吗? 如果没有,我们还有其他选择吗? n + 1问题使应用程序花费几秒钟来加载1000行。
编辑
感谢您的答复。 我知道我可以使用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();
}
}
}
}
如果需要渲染客户端,则需要使用Include()
来获取有关最近更新和投资数量( Count()
)的信息,实际上我需要从数据库中获取所有信息。 但是,如果我使用像
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();
我可以减少查询,只获取所需的信息( 在LastUpdatedBy的情况下,我需要将属性更改为getter / setter,这不是什么大问题,因为它仅用于应用程序的此特定部分 )。
如果我将Select()与这种方法(投影然后是映射)一起使用,则EF不考虑Include()
部分。
不知道要做什么,您使用的是lambda表达式而不是linq,而第二个选择是不必要的。
data.client
是client
, data.Countries
是client.Countries
,所以data.client.Countries = data.Countries
送花儿给人真实的。
如果您不希望延迟加载Countries
,请使用_dbContext.Clients.Include("Countries").Where() or select ()
。
为了强制渴望加载虚拟属性,您应该使用Include扩展方法。 这是MSDN https://msdn.microsoft.com/zh-cn/library/jj574232(v=vs.113).aspx的链接。
所以这样的事情应该工作:
return _dbContext.Clients.Include(c=>c.Countries).ToList();
如果我理解正确,可以尝试一下
_dbContext.LazyLoading = false;
var clientWithCountres = _dbContext.Clients
.Include(c=>c.Countries)
.ToList();
这将获取
Client
,只包括它Countries
。 如果禁用延迟加载,则不会从查询中加载其他集合。 除非您指定包含或投影。
仅供参考:Projection和Include()不能一起使用,请参见以下答案:如果您正在投影,它将绕过include。 https://stackoverflow.com/a/7168225/1876572
我不是100%肯定,但是我认为您的问题是,您仍将维护内部查询的可查询性,直到查询结束。
这个可查询的是惰性的(因为在模型中它是惰性的),并且您没有做任何解释来说明情况并非如此,您只是将相同的惰性可查询项投影到结果集中。
我无法直截了当地告诉您什么是正确的答案,但是我会尝试以下方法:
1也使用内部可投影的投影,例如
return _dbContext.Clients
.Select(client => new
{
client,
Countries = client.Countries.Select(c=>c)// or a new Country
})
2将include放在查询的末尾(我很确定include应用于结果而不是输入。如果将其放在投影之前,它肯定不起作用)例如:
_dbContext.Clients
.Select(client => new
{
client,
client.Countries
}.Include(c=>c.Countries)`
3尝试在投影内部指定枚举,例如:
_dbContext.Clients
.Select(client => new
{
client,
Countries = client.Countries.AsEnumerable() //perhaps tolist if it works
}`
我想通过说我还没有尝试以上任何方法来避免这种情况,但是我认为这将使您走上正确的道路。
IMO很少有延迟加载的好用例。 除非您的用户直接在模型上遵循惰性路径,否则它几乎总是会导致生成太多查询。 仅在极其谨慎的情况下使用它,而在请求响应(例如Web)应用程序中则完全不使用IMO。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.