LINQ Group By方法不生成預期的SQL

asp.net-core-1.1 c# entity-framework-core linq-to-entities sql-server-2012

以下LINQ查詢應該返回每個用戶的登錄次數:

控制器

var lst = _context.LoginHistory.GroupBy(l => l.UserName).Select(lg => new { user_name = lg.Key, cnt = lg.Count() });

 return View(lst.ToList());

SQL Profiler of SQL Server 2012SQL Profiler of SQL Server 2012返回以下奇怪的查詢:

SQL事件探查器輸出:

SELECT [l].[LoginHistory], [l].[Hit_Count], [l].[LastLogin], [l].[UserName]
FROM [LoginHistory] AS [l]
ORDER BY [l].[UserName]

型號

public class LoginHistory
{
   public int LoginHistoryId { get; set; }
   public string UserName { get; set; }
   public int Hit_Count { get; set; }
   public DateTime LoginDate { get; set; }
}

注意

  1. 我不知道為什麼甚至列Hit_Count都在探查器輸出查詢中,因為它在這裡不起任何作用 - 我正在嘗試的是顯示每個用戶的登錄總數。而且,在SQL Profiler輸出中,我期待類似於以下t-sql:
  2. 它是應用程序執行的唯一LINQ qry,因此我不是錯誤地在SQL事件探查器中選錯了SQL
  3. 視圖中的結果也不正確[實際上導致我對本文中顯示的所有調查]
  4. 可能是另一個EF Core 1.1.1錯誤,另一個用戶在這裡指出了另一個錯誤

預期[或類似的] SQL事件探查器輸出:

SELECT username, COUNT(*)
FROM LoginHistory
GROUP BY username

一般承認的答案

很多人在使用SQL + LINQ + Entity Framework時會感到驚訝,當他們想要運行像你這樣的簡單聚合函數時,發現Sql Profiler沒有反映聚合併顯示與通用SELECT * FROM table非常相似的東西SELECT * FROM table

雖然大多數使用LINQ和EF的應用程序也在使用數據庫服務器,但其他應用程序正在使用或正在使用其他數據源(例如XML,平面文件,Excel電子表格)中的數據並將其映射到應用程序的實體/模型/類中。

因此,在LINQ中聚合數據時的正常操作模式是加載和映射資源數據,然後在應用程序中執行所需的功能。

這可能對一些人來說很好,但在我的情況下我有限的應用程序服務器資源和大量的數據庫資源,所以我選擇將這些函數轉移到我的SQL Server上,然後在類中創建一個方法來使用ADO並執行原始SQL 。

應用於您的特定型號我們會有類似的東西,它可能會有所不同,具體取決於您的特定編碼風格和任何適用的標準。

public class LoginHistory {
    public int LoginHistoryId { get; set; }
    public string UserName { get; set; }
    public int Hit_Count { get; set; }
    public DateTime LoginDate { get; set; }

    public List<LoginHistory> GetList_LoginTotals() {
        List<LoginHistory> retValue = new List<LoginHistory>();

        StringBuilder sbQuery = new StringBuilder();
        sbQuery.AppendLine("SELECT username, COUNT(*) ");
        sbQuery.AppendLine("FROM LoginHistory ");
        sbQuery.AppendLine("GROUP BY username");

        using (SqlConnection conn = new SqlConnection(strConn)) {
            conn.Open();
            using (SqlCommand cmd = new SqlCommand(sbQuery.ToString(), conn)) {
                cmd.CommandType = CommandType.Text;
                using (SqlDataReader reader = cmd.ExecuteReader()) {
                    while (reader.Read()) {
                        var row = new LoginHistory {
                            UserName = reader.GetString(0)
                            , Hit_Count = reader.GetInt32(1)
                        };
                        retValue.Add(row);
                    }
                }
            }
            conn.Close();
        }
        return retValue;
    }
}

並且您的控制器代碼可以更新為類似於此的內容:

var LoginList = new LoginHistory().GetList_LoginTotals(); 
return View(LoginList);

// or the one liner: return View(new LoginHistory().GetList_LoginTotals());

熱門答案

這次它並不是一個真正的錯誤(根據EF Core團隊的說法),但功能不完整(因為在EF6中它的工作方式與預期相符)。您可以在EF核心路線圖中看到它“記錄”:

在我們說EF Core是EF的推薦版本之前我們認為我們需要的東西。在我們實現這些功能之前,EF Core將是許多應用程序的有效選項,特別是在諸如UWP和.NET Core等平台上,其中EF6.x不起作用,但對於許多應用程序而言,缺少這些功能將使EF6.xa成為更好的選擇。

接著

GroupBy轉換將LINQ GroupBy運算符的轉換移動到數據庫,而不是內存中。

所謂的客戶評估(以前的EF版本中不存在的EF Core的一個特性)是所有邪惡的根源。它允許EF Core在內存中“成功”處理許多查詢,從而引入性能問題(儘管根據定義它們應該產生正確的結果)。

這就是為什麼我建議總是打開EF Core Logging來監控你的查詢真正發生了什麼。例如,對於示例查詢,您將看到以下警告:

The LINQ expression 'GroupBy([l].UserName, [l])' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.

The LINQ expression 'GroupBy([l].UserName, [l])' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.

The LINQ expression 'Count()' could not be translated and will be evaluated locally. To configure this warning use the DbContextOptionsBuilder.ConfigureWarnings API (event id 'RelationalEventId.QueryClientEvaluationWarning'). ConfigureWarnings can be used when overriding the DbContext.OnConfiguring method or using AddDbContext on the application service provider.

您還可以通過在DbContext OnConfiguring覆蓋中添加以下內容來關閉客戶端評估:

optionsBuilder.ConfigureWarnings(bulder => bulder.Throw(RelationalEventId.QueryClientEvaluationWarning));

但現在您將從該查詢中簡單地獲取運行時異常。

如果這對您來說很重要,那麼您可能屬於應用程序類別, 缺少這些功能將使EF6.xa成為更好的選擇



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow