簡體   English   中英

實體框架:Count() 在大型 DbSet 和復雜的 WHERE 子句上非常慢

[英]Entity Framework: Count() very slow on large DbSet and complex WHERE clause

我需要使用相對復雜的表達式作為WHERE子句對此實體框架 (EF6) 數據集執行計數操作,並期望它返回大約 100k 條記錄。

計數操作顯然是記錄被物化的地方,因此是最慢的操作。 在我們的生產環境中,計數操作大約需要 10 秒,這是不可接受的。

請注意,該操作是直接在 DbSet 上執行的(db 是 Context 類),因此不應發生延遲加載。

如何進一步優化此查詢以加快流程?

主要用例是顯示具有多個過濾條件的索引頁面,但 function 也用於根據服務類中的其他操作的需要將通用查詢寫入ParcelOrders表,這可能是一個壞主意,導致非常復雜的查詢懶惰,可能會成為未來的問題。 該計數稍后用於分頁,實際顯示的記錄數(例如 500 條)要少得多。 這是一個使用 SQL 服務器的數據庫優先項目。

ParcelOrderSearchModel是一個 C# 類,用於封裝查詢參數,僅由服務類使用,以便調用GetMatchingOrders function。 請注意,在大多數調用中, ParcelOrderSearchModel的大部分參數將為 null。

public List<ParcelOrderDto> GetMatchingOrders(ParcelOrderSearchModel searchModel)
{
        // cryptic id known --> allow public access without login
        if (String.IsNullOrEmpty(searchModel.KeyApplicationUserId) && searchModel.ExactKey_CrypticID == null)
            throw new UnableToCheckPrivilegesException();

        Func<ParcelOrder, bool> userPrivilegeValidation = (x => false);

        if (searchModel.ExactKey_CrypticID != null)
        {
            userPrivilegeValidation = (x => true);
        }
        else if (searchModel.KeyApplicationUserId != null)
            userPrivilegeValidation = privilegeService.UserPrivilegeValdationExpression(searchModel.KeyApplicationUserId);

        var criteriaMatchValidation = CriteriaMatchValidationExpression(searchModel);
    
        var parcelOrdersWithNoteHistoryPoints = db.HistoryPoint.Where(hp => hp.Type == (int)HistoryPointType.Note)
            .Select(hp => hp.ParcelOrderID)
            .Distinct();

        Func<ParcelOrder, bool> completeExpression = order => userPrivilegeValidation(order) && criteriaMatchValidation(order);
        searchModel.PaginationTotalCount = db.ParcelOrder.Count(completeExpression);
       
        // todo: use this count for pagination
}


public Func<ParcelOrder, bool> CriteriaMatchValidationExpression(ParcelOrderSearchModel searchModel)
{
        Func<ParcelOrder, bool> expression =
            po => po.ID == 1;

        expression =
           po =>
           (searchModel.KeyUploadID == null || po.UploadID == searchModel.KeyUploadID)
       && (searchModel.KeyCustomerID == null || po.CustomerID == searchModel.KeyCustomerID)
       && (searchModel.KeyContainingVendorProvidedId == null || (po.VendorProvidedID != null && searchModel.KeyContainingVendorProvidedId.Contains(po.VendorProvidedID)))
       && (searchModel.ExactKeyReferenceNumber == null || (po.CustomerID + "-" + po.ReferenceNumber) == searchModel.ExactKeyReferenceNumber)
       && (searchModel.ExactKey_CrypticID == null || po.CrypticID == searchModel.ExactKey_CrypticID)
       && (searchModel.ContainsKey_ReferenceNumber == null || (po.CustomerID + "-" + po.ReferenceNumber).Contains(searchModel.ContainsKey_ReferenceNumber))
       && (searchModel.OrKey_Referencenumber_ConsignmentID == null ||
               ((po.CustomerID + "-" + po.ReferenceNumber).Contains(searchModel.OrKey_Referencenumber_ConsignmentID)
               || (po.VendorProvidedID != null && po.VendorProvidedID.Contains(searchModel.OrKey_Referencenumber_ConsignmentID))))
       && (searchModel.KeyClientName == null || po.Parcel.Name.ToUpper().Contains(searchModel.KeyClientName.ToUpper()))
       && (searchModel.KeyCountries == null || searchModel.KeyCountries.Contains(po.Parcel.City.Country))
       && (searchModel.KeyOrderStates == null || searchModel.KeyOrderStates.Contains(po.State.Value))
       && (searchModel.KeyFromDateRegisteredToOTS == null || po.DateRegisteredToOTS > searchModel.KeyFromDateRegisteredToOTS)
       && (searchModel.KeyToDateRegisteredToOTS == null || po.DateRegisteredToOTS < searchModel.KeyToDateRegisteredToOTS)
       && (searchModel.KeyFromDateDeliveredToVendor == null || po.DateRegisteredToVendor > searchModel.KeyFromDateDeliveredToVendor)
       && (searchModel.KeyToDateDeliveredToVendor == null || po.DateRegisteredToVendor < searchModel.KeyToDateDeliveredToVendor);
        return expression;
}

public Func<ParcelOrder, bool> UserPrivilegeValdationExpression(string userId)
{
        var roles = GetRolesForUser(userId);

        Func<ParcelOrder, bool> expression =
            po => po.ID == 1;
        if (roles != null)
        {
            if (roles.Contains("ParcelAdministrator"))
                expression =
                    po => true;

            else if (roles.Contains("RegionalAdministrator"))
            {
                var user = db.AspNetUsers.First(u => u.Id == userId);
                if (user.RegionalAdministrator != null)
                {
                    expression =
                        po => po.HubID == user.RegionalAdministrator.HubID;
                }
            }
            else if (roles.Contains("Customer"))
            {
                var customerID = db.AspNetUsers.First(u => u.Id == userId).CustomerID;
                expression =
                    po => po.CustomerID == customerID;
            }
            else
            {
                expression =
                    po => false;
            }
        }

        return expression;
}

如果您可以避免它,請不要計入分頁。 只需返回第一頁。 計數總是很昂貴,而且對用戶體驗的影響很小。

無論如何,您構建的動態搜索都是錯誤的。

您正在調用IEnumerable.Count(Func<ParcelOrder,bool>) ,這將強制客戶端評估您應該調用IQueryable.Count(Expression<Func<ParcelOrder,bool>>) 這里:

    Func<ParcelOrder, bool> completeExpression = order => userPrivilegeValidation(order) && criteriaMatchValidation(order);
    searchModel.PaginationTotalCount = db.ParcelOrder.Count(completeExpression);

但是在 EF 中有一個更簡單、更好的模式:只需有條件地將條件添加到您的 IQueryable。

例如,在您的 DbContext 上放置一個方法,如下所示:

public IQueryable<ParcelOrder> SearchParcels(ParcelOrderSearchModel searchModel)
{
        var q = this.ParcelOrders();
        if (searchModel.KeyUploadID != null)
        {
          q = q.Where( po => po.UploadID == searchModel.KeyUploadID );
        }
        if (searchModel.KeyCustomerID != null)
        {
          q = q.Where( po.CustomerID == searchModel.KeyCustomerID );
        }
        //. . .
        return q;
}

暫無
暫無

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

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