Entity Framework Core 可讓您在使用關係資料庫時,下拉至 SQL 查詢。 如果想要的查詢無法使用 LINQ 表示,或 LINQ 查詢導致 EF 產生效率不佳的 SQL,SQL 查詢就很有用。 SQL 查詢可以傳回屬於模型一部分的一般實體類型或
無索引鍵實體類型
。
您可以在 GitHub 上檢視此文章的
範例
\(英文\)。
基本 SQL 查詢
您可以使用
FromSql
來根據 SQL 查詢來開始 LINQ 查詢:
var blogs = context.Blogs
.FromSql($"SELECT * FROM dbo.Blogs")
.ToList();
FromSql 已在 EF Core 7.0 中引進。 使用舊版時,請改用 FromSqlInterpolated 。
SQL 查詢可用來執行會傳回實體資料的預存程式:
var blogs = context.Blogs
.FromSql($"EXECUTE dbo.GetMostPopularBlogs")
.ToList();
FromSql 只能直接在 上使用 DbSet
。 無法透過任意 LINQ 查詢來撰寫。
使用 SQL 查詢時,請密切注意參數化
在 SQL 查詢中導入任何使用者提供的值時,請務必小心以避免 SQL 插入式攻擊。 當程式將使用者提供的字串值整合到 SQL 查詢中,並製作使用者提供的值以終止字串並執行另一個惡意 SQL 作業時,就會發生 SQL 插入。 若要深入瞭解 SQL 插入, 請參閱此頁面。
FromSql和 FromSqlInterpolated 方法對 SQL 插入是安全的,而且一律將參數資料整合為個別的 SQL 參數。 不過,如果不當使用,方法 FromSqlRaw 可能會容易受到 SQL 插入式攻擊。 詳細資訊請見下文。
下列範例會將單一參數傳遞至預存程式,方法是在 SQL 查詢字串中包含參數預留位置,並提供額外的引數:
var user = "johndoe";
var blogs = context.Blogs
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
.ToList();
雖然此語法看起來可能像一般 C# 字串內插補點,但所提供的值會包裝在 中 DbParameter
,並插入指定預留位置的 {0}
產生的參數名稱。 這可避免 >FromSql SQL 插入式攻擊,並有效率且正確地將值傳送至資料庫。
執行預存程式時,在 SQL 查詢字串中使用具名參數會很有用,特別是當預存程式具有選擇性參數時:
var user = new SqlParameter("user", "johndoe");
var blogs = context.Blogs
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser={user}")
.ToList();
如果您需要對所傳送之資料庫參數有更多的控制權,您也可以建構 DbParameter
,並將它提供為參數值。 這可讓您設定參數的精確資料庫類型,或 Facet,例如其大小、有效位數或長度:
var user = new SqlParameter("user", "johndoe");
var blogs = context.Blogs
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
.ToList();
您傳遞的參數必須完全符合預存程式定義。 請特別注意參數的排序,小心不要遺漏或錯置其中任何參數,或考慮使用具名參數標記法。 此外,請確定參數類型對應,並視需要設定其 Facet (大小、精確度、小數位數) 。
動態 SQL 和參數
FromSql 和其參數化應該盡可能使用。 不過,在某些情況下,SQL 需要動態分割在一起,而且無法使用資料庫參數。 例如,假設 C# 變數保存要篩選的屬性名稱。 可能很想要使用 SQL 查詢,如下所示:
var propertyName = "User";
var propertyValue = "johndoe";
var blogs = context.Blogs
.FromSql($"SELECT * FROM [Blogs] WHERE {propertyName} = {propertyValue}")
.ToList();
此程式碼無法運作,因為資料庫不允許 (或架構) 的任何其他部分參數化資料行名稱。
首先,請務必考慮透過 SQL 或以動態方式建構查詢的影響。 接受來自使用者的資料行名稱,可能會讓他們選擇未編制索引的資料行,讓查詢執行的速度非常慢,並多載您的資料庫;或允許他們選擇包含您不想要公開之資料的資料行。 除了真正的動態案例之外,最好有兩個數據行名稱的兩個查詢,而不是使用參數化將它們折迭成單一查詢。
如果您決定要動態建構 SQL,則必須使用 FromSqlRaw ,這可讓您直接將變數資料插入 SQL 字串中,而不是使用資料庫參數:
var columnName = "Url";
var columnValue = new SqlParameter("columnValue", "http://SomeURL");
var blogs = context.Blogs
.FromSqlRaw($"SELECT * FROM [Blogs] WHERE {columnName} = @columnValue", columnValue)
.ToList();
在上述程式碼中,資料行名稱會使用 C# 字串插補直接插入 SQL。 您必須負責確定此字串值是安全的,如果它來自不安全的來源,請加以清理;這表示偵測特殊字元,例如分號、批註和其他 SQL 建構,並正確逸出或拒絕這類輸入。
另一方面,資料行值會透過 DbParameter
傳送,因此在 SQL 插入的臉部中是安全的。
使用 FromSqlRaw 時請特別小心,並一律確定值來自安全來源,或已正確清理。 SQL 插入式攻擊可能會對您的應用程式造成嚴重後果。
使用 LINQ 撰寫
您可以使用 LINQ 運算子,在初始 SQL 查詢之上撰寫;EF Core 會將 SQL 視為子查詢,並在資料庫中撰寫它。 下列範例會使用 SQL 查詢,從Table-Valued函式 (TVF) 選取。 然後使用 LINQ 進行篩選和排序來撰寫它。
var searchTerm = "Lorem ipsum";
var blogs = context.Blogs
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
.Where(b => b.Rating > 3)
.OrderByDescending(b => b.Rating)
.ToList();
上述查詢會產生下列 SQL:
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM (
SELECT * FROM dbo.SearchBlogs(@p0)
) AS [b]
WHERE [b].[Rating] > 3
ORDER BY [b].[Rating] DESC
Include
運算子可以用來載入相關資料,就像任何其他 LINQ 查詢一樣:
var searchTerm = "Lorem ipsum";
var blogs = context.Blogs
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
.Include(b => b.Posts)
.ToList();
使用 LINQ 撰寫需要您的 SQL 查詢是可組合的,因為 EF Core 會將提供的 SQL 視為子查詢。 可撰寫的 SQL 查詢通常會以 SELECT
關鍵字開頭,而且不能包含子查詢中不正確 SQL 功能,例如:
在 SQL Server 上,結尾的查詢層級提示 (例如,OPTION (HASH JOIN)
)
在 SQL Server, ORDER BY
子句中 SELECT
未搭配 OFFSET 0
OR TOP 100 PERCENT
使用的 子句
SQL Server不允許透過預存程序呼叫撰寫,因此任何嘗試將額外的查詢運算子套用至這類呼叫,都會導致 SQL 無效。 在 AsEnumerable 或 FromSqlRaw 之後 FromSql 使用 或 AsAsyncEnumerable ,確定 EF Core 不會嘗試透過預存程式撰寫。
使用 FromSql 或 FromSqlRaw 遵循與 EF Core 中任何其他 LINQ 查詢完全相同變更追蹤規則的查詢。 例如,如果查詢專案實體類型,預設會追蹤結果。
下列範例會使用 SQL 查詢,從Table-Valued函式 (TVF) 選取,然後使用 的呼叫 AsNoTracking
來停用變更追蹤:
var searchTerm = "Lorem ipsum";
var blogs = context.Blogs
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
.AsNoTracking()
.ToList();
查詢純量 (非實體) 類型
這項功能是在 EF Core 7.0 中引進。
雖然 FromSql 對於查詢模型中定義的實體很有用, SqlQuery 但可讓您輕鬆地透過 SQL 查詢純量、非實體類型,而不需要下拉至較低層級的資料存取 API。 例如,下列查詢會從 Blogs
資料表擷取所有識別碼:
var ids = context.Database
.SqlQuery<int>($"SELECT [BlogId] FROM [Blogs]")
.ToList();
您也可以透過 SQL 查詢撰寫 LINQ 運算子。 不過,由於 SQL 會變成子查詢,SQL EF 必須參考其輸出資料行,因此您必須將輸出資料行 Value
命名為 。 例如,下列查詢會傳回高於識別碼平均值的識別碼:
var overAverageIds = context.Database
.SqlQuery<int>($"SELECT [BlogId] AS [Value] FROM [Blogs]")
.Where(id => id > context.Blogs.Average(b => b.BlogId))
.ToList();
FromSql 可以搭配資料庫提供者所支援的任何純量類型使用。 如果您想要使用資料庫提供者不支援的類型,您可以使用 預先慣例組態 來為其定義值轉換。
SqlQueryRaw 允許動態建構 SQL 查詢,就像實體類型一樣 FromSqlRaw 。
執行非查詢 SQL
在某些情況下,可能需要執行不會傳回任何資料的 SQL,通常是為了修改資料庫中的資料,或呼叫不會傳回任何結果集的預存程式。 這可透過 ExecuteSql 來完成:
using (var context = new BloggingContext())
var rowsModified = context.Database.ExecuteSql($"UPDATE [Blogs] SET [Url] = NULL");
這會執行提供的 SQL,並傳回修改的資料列數目。 ExecuteSql 使用安全參數化來保護 SQL 插入,就像 一樣 FromSql ,而且 ExecuteSqlRaw 允許動態建構 SQL 查詢,就像查詢一樣 FromSqlRaw 。
在 EF Core 7.0 之前,有時必須使用 ExecuteSql
API 在資料庫上執行「大量更新」,如上所述;這比查詢所有相符的資料列,然後用來 SaveChanges
修改它們更有效率。 EF Core 7.0 引進 ExecuteUpdate 和 ExecuteDelete,可讓您透過 LINQ 表達有效率的大量更新作業。 建議您盡可能使用這些 API,而不是 ExecuteSql
。
從 SQL 查詢傳回實體類型時,有一些需要注意的限制:
SQL 查詢必須傳回實體類型之所有屬性的資料。
結果集中的資料行名稱必須符合屬性所對應的資料行名稱。 請注意,此行為與 EF6 不同;EF6 忽略 SQL 查詢的屬性對資料行對應,而結果集資料行名稱必須符合這些屬性名稱。
SQL 查詢不能包含相關資料。 不過,在許多情況下,您可以使用 Include
運算子來傳回相關資料以在查詢上方進行撰寫 (請參閱包含相關資料)。