2016-11-17 75 views
0

我們的前端UI有一個過濾系統,在後端運行數百萬行。它使用在邏輯過程中構建的IQueryable,然後一次執行所有內容。每個單獨的UI組件都進行了與運算(例如,Dropdown1和Dropdown2將只返回具有共同選定內容的行)。這不是問題。但是,Dropdown3中有兩種類型的數據,檢查的項目需要ORd在一起,然後與查詢的其餘部分進行與運算。Linq查詢超時,如何簡化查詢

由於大量的行正在進行操作,它會保持超時。由於需要發生一些額外的連接,所以這有點棘手。這裏是我的代碼,用表名稱進行替換:

//The end list has driver ids in it--but the data comes from two different places. Build a list of all the driver ids. 
driverIds = db.CarDriversManyToManyTable.Where(
         cd => 
          filter.CarIds.Contains(cd.CarId) && //get driver IDs for each car ID listed in filter object 
          ).Select(cd => cd.DriverId).Distinct().ToList(); 

driverIds = driverIds.Concat(
        db.DriverShopManyToManyTable.Where(ds => filter.ShopIds.Contains(ds.ShopId)) //Get driver IDs for each Shop listed in filter object 
         .Select(ds => ds.DriverId) 
         .Distinct()).Distinct().ToList(); 
//Now we have a list solely of driver IDs 

//The query operates over the Driver table. The query is built up like this for each item in the UI. Changing from Linq is not an option. 
query = query.Where(d => driverIds.Contains(d.Id)); 

我如何可以簡化這個查詢,使我沒有找回ID的成千上萬到內存中,然後給他們回SQL?

回答

3

有幾種方法可以生成單個SQL查詢。他們需要保留IQueryable<T>類型的查詢的部分,即請勿使用ToList,ToArray,AsEnumerable等方法強制它們在內存中執行和評估。

一種方法是創建一個包含過濾IDS(這將是通過定義唯一的),並使用join操作者施加它的主查詢Union查詢:

var driverIdFilter1 = db.CarDriversManyToManyTable 
    .Where(cd => filter.CarIds.Contains(cd.CarId)) 
    .Select(cd => cd.DriverId); 
var driverIdFilter2 = db.DriverShopManyToManyTable 
    .Where(ds => filter.ShopIds.Contains(ds.ShopId)) 
    .Select(ds => ds.DriverId); 
var driverIdFilter = driverIdFilter1.Union(driverIdFilter2); 
query = query.Join(driverIdFilter, d => d.Id, id => id, (d, id) => d); 

另一種方式,可以使用兩種或 - 編Any基礎條件,這將轉化爲EXISTS(...) OR EXISTS(...) SQL查詢過濾器:

query = query.Where(d => 
    db.CarDriversManyToManyTable.Any(cd => d.Id == cd.DriverId && filter.CarIds.Contains(cd.CarId)) 
    || 
    db.DriverShopManyToManyTable.Any(ds => d.Id == ds.DriverId && filter.ShopIds.Contains(ds.ShopId)) 
); 

你可以試試,看看哪一個性能更好。

1

這個問題的答案很複雜,並且有很多方面可以單獨幫助您解決您的具體問題。

首先,考慮使用分頁。 .Skip(PageNum * PageSize).Take(PageSize)我懷疑你的用戶需要在前端一次查看數百萬行。只向他們展示100個,或者其他較小的數字對你來說似乎是合理的。

你已經提到你需要使用連接來獲取你需要的數據。這些連接可以在形成你的IQueryable(實體框架)時完成,而不是在內存中(linq到對象)。閱讀linq中的join語法。但是,在LINQ中執行顯式連接並不是最佳實踐,尤其是在您自己設計數據庫的情況下。如果您正在執行數據庫第一代實體,請考慮在您的表上放置外鍵約束。這將允許數據庫優先實體生成選擇它們併爲您提供導航屬性,這將大大簡化您的代碼。

但是,如果您對數據庫設計沒有任何控制權或影響力,那麼我建議您先在SQL中構建您的查詢以查看其執行方式。在那裏進行優化直到獲得所需的性能,然後將其轉換爲實體框架linq查詢,該查詢使用顯式連接作爲最後的手段。

要加快查詢速度,您可能需要對所有要加入的「關鍵」列執行索引。找出需要提高性能的索引的最佳方法是,將由EF linq生成的SQL查詢帶入SQL Server Management Studio。從那裏,更新生成的SQL,爲您的@p參數提供一些預定義的值,僅作爲示例。完成此操作後,右鍵單擊查詢並使用顯示預計執行計劃或包含實際執行計劃。如果索引可以提高您的查詢性能,那麼這個功能很有可能會告訴您這個問題,甚至爲您提供腳本來創建所需的索引。

1

在我看來,使用實例版本的LINQ擴展在創建完成之前創建了多個集合。使用從聲明版本應該削減相當多:

driveIds = (from var record in db.CarDriversManyToManyTable 
      where filter.CarIds.Contains(record.CarId) 
      select record.DriverId).Concat 
      (from var record in db.DriverShopManyToManyTable 
      where filter.ShopIds.Contains(record.ShopId) 
      select record.DriverId).Distinct() 

同樣使用groupby擴展會比查詢每個驅動程序Id更好的性能。