簡體   English   中英

Linq查詢超時,如何簡化查詢

[英]Linq query timing out, how to streamline query

我們的前端UI具有一個過濾系統,該系統在后端可處理數百萬行。 它使用一個在邏輯過程中建立的IQueryable,然后立即執行所有操作。 每個單獨的UI組件都進行“與”運算(例如,Dropdown1和Dropdown2將僅返回具有相同選擇的行)。 這不是問題。 但是,Dropdown3中包含兩種類型的數據,並且需要對選中的項進行“或”運算,然后與其余查詢進行“與”運算。

由於要進行大量操作,因此會一直超時。 由於還需要進行其他一些聯接,因此有些棘手。 這是我的代碼,替換了表名:

//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?

產生單個SQL查詢的方法有多種。 它們所需要的所有內容都必須保持IQueryable<T>類型的查詢,即不要使用ToListToArrayAsEnumerable等方法來強制它們在內存中執行和評估。

一種方法是創建包含過濾后的ID(根據定義將是唯一的)的Union查詢,並使用join運算符將其應用於主查詢:

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

另一種方法是使用兩個基於OR-ed 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))
);

您可以嘗試看看哪個效果更好。

這個問題的答案很復雜,有很多方面,個別情況可能會也可能不會有幫助。

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

您已經提到需要使用聯接來獲取所需的數據。 這些連接可以在形成IQueryable(實體框架)時完成,而不是在內存中(對對象的限制)完成。 在linq中閱讀聯接語法。

但是,在LINQ中執行顯式聯接不是最佳實踐,尤其是在您自己設計數據庫時。 如果您正在執行數據庫的第一代實體,請考慮在表上放置外鍵約束。 這將允許數據庫優先實體生成來拾取它們,並為您提供導航屬性,這將大大簡化您的代碼。

但是,如果您對數據庫設計沒有任何控制或影響,那么建議您首先使用SQL構造查詢以查看其性能。 在那里進行優化,直到獲得所需的性能,然后將其轉換為使用顯式連接作為最后手段的實體框架linq查詢。

為了加快此類查詢的速度,您可能需要對要加入的所有“關鍵”列進行索引。 找出需要哪些索引以提高性能的最佳方法,使用EF linq生成的SQL查詢,並將其帶入SQL Server Management Studio。 從那里開始,更新生成的SQL,以為您的@p參數提供一些預定義的值,僅作為示例。 完成此操作后,右鍵單擊查詢,然后使用顯示估算的執行計划或包括實際的執行計划。 如果建立索引可以改善查詢性能,則很有可能該功能將告訴您有關該索引的信息,甚至可以為您提供創建所需索引的腳本。

在我看來,使用LINQ擴展的實例版本會在完成之前創建多個集合。 使用from語句版本應將其削減很多:

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()

與查詢每個驅動程序ID相比,使用groupby擴展還將提供更好的性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM