FromSqlRaw和ExecuteSqlRaw必须手动参数化,字段名、表名、排序方向等标识符需白名单校验,动态拼接字符串或插值均不安全,仅命名参数+SqlParameter有效。

FromSqlRaw 和 ExecuteSqlRaw 必须手动参数化
EF Core 不会自动帮你参数化 FromSqlRaw 或 ExecuteSqlRaw 里的字符串——传进去什么,数据库就执行什么。你写 FromSqlRaw("SELECT * FROM Users WHERE Name = '" + name + "'"),恶意输入在 C# 层就已拼进 SQL,数据库收到的就是完整可执行语句。
- ✅ 正确做法:只用命名参数(
@name)+SqlParameter,例如:FromSqlRaw("WHERE Name = @name", new SqlParameter("@name", name)) - ✅ EF Core 5+ 支持链式
AddParameter,但仅限FromSqlRaw返回的IQueryable后续调用,ExecuteSqlRaw不支持 - ❌ 错误写法:
FromSqlRaw($"SELECT * FROM Users WHERE Name = '{name}'")—— 字符串插值不等于参数化,仍是拼接 - ❌ 危险写法:
FromSqlInterpolated($"SELECT * FROM Users WHERE Name LIKE {name + "%"}")—— 插值后 C# 层已拼好字符串,EF 来不及拦截
字段名、表名、ORDER BY 不能参数化,必须白名单校验
数据库引擎根本不支持参数化标识符(identifier),SqlParameter 传字段名只会被当作文本值处理,比如 ORDER BY @sortField 实际执行的是 ORDER BY 'Email',排序失效且可能报错。
- ✅ 动态字段/表名必须硬编码或来自预设白名单:
new[] { "Name", "Email", "CreatedAt" }.Contains(sortField),不匹配直接拒掉 - ✅ 排序方向(ASC/DESC)同理,不能拼:
ORDER BY {sortField} {sortDir},而应判断后硬编码:sortDir == "desc" ? "ORDER BY Name DESC" : "ORDER BY Name ASC" - ❌ 反射取属性名(
u.GetType().GetProperty(fieldName))是高危操作,等价于把用户输入当代码执行 - ❌ 用
string.Format或Replace手动“转义”字段名,不可靠,且易被绕过
Where 表达式树里别偷偷拼字符串
_context.Users.Where(u => u.Email == email) 安全,是因为 EF 把 email 编译成参数 @p0;但一旦你把表达式树拆开再拼,就可能绕过参数化机制。
- ✅ 坚持用标准 LINQ 方法链,避免调用
.ToString()、Expression.ToString()或手动转成字符串 - ❌ 不要用
Expression.AndAlso动态组合表达式树后编译成Func—— 这会导致内存过滤(In-Memory Evaluation),且字段名若来自用户输入,就是列名注入 - ❌ 别写
Where($"u.{fieldName} == {value}")这类伪动态查询,EF 不识别,实际是字符串拼接 - ⚠️ 注意:即使用了表达式树,字段名仍必须是常量或白名单控制,不能由用户输入直接驱动
FromSqlRaw 查询结果类型要严格匹配
返回字段名和类型不匹配实体时,EF 可能静默忽略字段或抛异常,但更危险的是:你为了“适配”而改写 SQL,无意中引入拼接逻辑。
- ✅ 返回非完整实体时,用
AsNoTracking()+ 显式投影到 DTO 或匿名对象:.Select(x => new { x.Name, x.Email }) - ✅ 自定义 DTO 类需与 SQL 字段名、顺序、类型一致;大小写敏感(尤其 PostgreSQL)
- ❌ 直接用
SqlQueryRaw<object>()</object>或泛型T但字段不匹配,可能导致运行时映射失败或空值覆盖 - ❌ 为图省事,在 SQL 里用
CASE WHEN或函数包裹字段再映射,增加 SQL 复杂度,提高出错概率
字段名和排序方向这类 SQL 结构部分,永远无法靠参数化保护,白名单不是可选项,是唯一安全路径。

















