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