簡體   English   中英

"在“Where”lambda 表達式 [EF-CORE 3.1] 中添加函數后,EF 核心給出錯誤"

[英]EF core gives error after adding a function inside "Where" lambda expression [EF-CORE 3.1]

我添加了函數UserHasFilter<\/code>函數,這樣我就可以過濾並查看用戶是否具有按照您所看到的邏輯的過濾器,但是當我運行它時會出現以下錯誤:

我不知道我是否使用了正確的過濾方法或有更好的方法? 我也不知道錯誤是如何發生的。

這是我的代碼:

 public async Task<IEnumerable<ConditionDataModel>> GetUserFilters(string pageName)
        {
            var user = await _configurationService.GetCurrentUser();
            if (user == null)
            {
                return null;
            }
            var conditions = _context.FilterUserGroups
                .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
                .Include(f => f.FilterUsers).ThenInclude(d => d.User)
                .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
                .Where(f => f.CompanyDataRight.Page.ClassName == pageName && UserHasFilter(user.Id, f))
                .Include(f => f.Conditions)
                .SelectMany(f => f.Conditions)
                .Distinct()
                .AsEnumerable();
            return conditions;
        }

        public virtual bool UserHasFilter(Guid userId, FilterUserGroupDataModel filterUserGroup)
        {
            if(filterUserGroup == null)
            {
                return false;
            }
            if (filterUserGroup.FilterUsers?.Any(u => u.User.Id == userId) == true)
            {
                return true;
            }

            return false;
        }

重要的是要了解實體框架提供程序只能將 LINQ表達式樹<\/em>轉換為 SQL 語句。 當涉及到常見的IEnumerable<\/code>函數(如IEnumerable<T>.Contains<\/code> )或 CLR 函數(如String.ToUpper()<\/code>時,提供程序將這些常見的 C# 函數調用映射<\/em>到已知的 SQL 實現。

這就是為什么您的標准自定義函數無法轉換<\/em>為 SQL 的原因,提供者根本沒有相應的已知<\/em>實現。 這樣想,如果我用反射來檢查一個方法,我只得到原型,所以名稱,輸入和返回類型,沒有辦法訪問該方法的內部工作。 並非所有<\/strong>CLR 函數都已映射也是事實,因此當您嘗試使用提供程序無法識別的 CLR 函數時,您將看到同樣的錯誤。

即使錯誤消息建議了以下選項:

如果此方法可以映射到您的自定義函數,則以可翻譯的形式重寫查詢,或通過插入對“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的調用顯式切換到客戶端評估

關於 SO 的許多答案只是告訴 OP 切換到客戶端評估,而實際上這是一種hack<\/em> ,並且通常有更好的解決方案,特別是對於我們封裝常見業務表達式以供重用的自定義函數。 我們專門編寫這些是因為我們希望它們被定義一次並重復使用,現在讓我們看看如何<\/em>正確編寫它們!

過濾器的客戶評估是一種反模式<\/em>!<\/strong>
是的,異常消息將客戶評估<\/em>列為潛在<\/em>選項,但不要被愚弄。 如果您將整個數據集具體化到內存中,然后應用過濾器,那么您可能會浪費網絡帶寬、CPU 滴答聲和執行時間。 如果結果集很大並且過濾器的結果為零<\/em>記錄,那么您就浪費了很多<\/strong>資源! 在 LINQ-to-SQL 場景中,我們真的<\/em>希望不惜一切代價<\/em>避免<\/em>客戶端過濾!

不要偷懶,正確地去做! 具有諷刺意味的是,如果您正確構建了 LINQ 表達式,您可能一開始就不會考慮客戶端評估<\/em>,這很可能導致進一步降低查詢性能,並且很容易產生堆棧溢出異常或違反其他內存約束。

表達式<Func<>><\/h3>

您可以專門為 LINQ 定義自定義 C# 方法,使它們以表達式樹<\/em>的形式返回<\/em>lambda 表達式<\/em>,即Expression<Func<>><\/code> 。

並非所有 C# 函數,尤其是“自定義”C# 函數都不能由實體框架提供程序轉換為 SQL,並且從 EF Core 3.x 實體框架開始,當它嘗試從服務器端評估以靜默方式切換到客戶端時將引發異常側面評價。 要解決您的問題,有兩種解決方案。

  1. 通過提前調用AsEnumerable()手動切換到客戶端評估。
  2. 重寫您的 LINQ 查詢,以便 EF Core 可以將其轉換為 SQL。

以下是如何做#2:

var conditions = _context.FilterUserGroups
    .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
    .Include(f => f.FilterUsers).ThenInclude(d => d.User)
    .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
    .Where(f => f.CompanyDataRight.Page.ClassName == pageName && (f.FilterUsers != null && f.FilterUsers.Any(u => u.User.Id == user.Id)))
    .Include(f => f.Conditions)
    .SelectMany(f => f.Conditions)
    .Distinct()
    .AsEnumerable();

應該有效(我目前無法測試)。 我所做的是將您的方法調用內聯重寫為語句,EF Core 應該能夠將其轉換為 SQL。 如果沒有(並且您無法自己修復它),總是有選項 #1:切換到客戶端評估,這就是您“最佳”這樣做的方式:

var conditions = _context.FilterUserGroups
    .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
    .Include(f => f.FilterUsers).ThenInclude(d => d.User)
    .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
    .Include(f => f.Conditions)
    .SelectMany(f => f.Conditions)
    .Distinct()
    .AsEnumerable()
    .Where(f => f.CompanyDataRight.Page.ClassName == pageName && UserHasFilter(user.Id, f));

看看我是如何在AsEnumerable之后移動Where的,當您調用AsEnumerable時,EF 將對象加載到內存中,這意味着您可以對它們執行任何操作。 這充其量是次優的,因為現在它加載到內存中的對象比實際應該多,但有時執行更復雜查詢的唯一方法是在內存中執行它們*。 但是,此解決方案確實有一個好處:從此類派生的類可以覆蓋UserHasFilter方法,更改查詢邏輯而無需重新創建所述查詢。


* 並不是說​​僅使用 SQL 就無法實現這一點,只是 EF 無法將每個 LINQ 查詢轉換為 SQL

暫無
暫無

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

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