必须使用参数化防止SQL注入:FromSqlRaw仅支持{0}位置占位符或@param命名参数配合SqlParameter;FromSqlInterpolated仅安全处理纯变量插值,表名/字段名/ORDER BY等结构部分须白名单校验。

必须用参数化,不能拼字符串——FromSqlRaw 和 ExecuteSqlRaw 本身不防注入,EF Core 不会帮你转义或校验传进去的 SQL 字符串。
FromSqlRaw 怎么写才安全
它只认两种参数形式:位置占位符 {0}、{1},或命名参数 @param 配合 SqlParameter。任何用 + 或 $"" 拼接用户输入进 SQL 字符串的行为,都在 C# 层就已完成注入。
- ✅ 安全:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", email) - ✅ 安全:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Status = @status", new SqlParameter("@status", "Active")) - ❌ 危险:
context.Users.FromSqlRaw($"SELECT * FROM Users WHERE Email = '{email}'") - ❌ 危险:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Email = '" + email + "'")
常见错误现象:InvalidOperationException: The required column 'xxx' was not present in the results,往往是因为参数名写错(比如 SQL 里写 @id,却传了 new SqlParameter("@Id", 123)),导致 SQL 执行失败返回空结果集。
FromSqlInterpolated 看似省事,但有硬限制
它自动把插值变量(如 {email})转成命名参数,底层走 ADO.NET 参数绑定,确实比手写 SqlParameter 少出错。但它只处理纯变量,不处理表达式或字符串拼接逻辑。
- ✅ 安全:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}") - ✅ 安全:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE CreatedAt > {DateTime.UtcNow.AddDays(-7)}") - ❌ 危险:
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Name LIKE {name + "%"}")——name + "%"是 C# 表达式,结果整体当参数,语义已错 - ❌ 危险:
context.Users.FromSqlInterpolated($"SELECT * FROM {tableName} WHERE ...")—— 表名无法参数化,语法非法
表名、列名、ORDER BY 这些结构部分怎么处理
数据库引擎根本不支持参数化标识符(identifier),FromSqlRaw("SELECT * FROM {0}", tableName) 在语法上就通不过,EF Core 也不会拦截或转义。这些必须靠白名单校验,而不是“想办法参数化”。
- 字段名映射前检查:
if (!new[] { "Name", "Email", "CreatedAt" }.Contains(fieldName)) throw new ArgumentException("非法字段名"); - 排序方向必须显式判断:
var orderClause = sortDirection == "DESC" ? "DESC" : "ASC";,再拼进 SQL - 禁止把用户输入直接用于
ORDER BY {sortField}或WHERE {condition}这类结构拼接
查非实体类型(DTO、统计、标量)时别踩坑
FromSqlRaw 只支持映射到上下文里注册过的实体类型。想查 COUNT(*)、聚合字段、或自定义结构,得换方法。
- 标量查询(如计数):
context.Database.SqlQueryRaw("SELECT COUNT(*) FROM Users").Single() - 自定义 DTO:
context.Database.GetDbConnection().CreateCommand()手动执行 + 读取DbDataReader,自己映射(绕过 EF 映射层,但失去变更跟踪) - 复杂报表场景:在数据库建
VIEW,然后为它创建对应实体并加入ModelBuilder - ⚠️ 特别注意:
FromSqlRaw(...).AsEnumerable().Where(...)会把全部数据拉到内存过滤,性能崩盘,且索引失效
最常被忽略的是:动态结构部分(表名、字段名、排序)根本不能参数化,白名单不是可选项,是唯一合法路径;而 FromSqlInterpolated 的“自动”只覆盖纯变量,一旦涉及拼接或表达式,安全边界立刻消失。










