EF Core 的 IQueryable 查询默认参数化防注入,但需避免字符串拼接、反射字段名及未参数化的 FromSqlRaw;原生 SQL 必须用占位符+ AddParameter 或 ExecuteSqlInterpolated。

Entity Framework Core 默认就是参数化的,但得用对方式
EF Core 的 IQueryable 查询(如 _context.Users.Where(u => u.Name == name))在生成 SQL 时,底层自动使用参数化查询,name 值不会拼进 SQL 字符串,而是作为独立参数传入。这是它防注入的默认行为,无需额外配置。
但前提是:你不能中途“破防”——比如把表达式转成字符串、手动拼接、或混用原始 SQL。
- 避免调用
.ToString()或Expression.ToString()获取 SQL 文本再执行 - 不要写
$"SELECT * FROM Users WHERE Name = '{name}'"然后交给FromSqlRaw - 禁止在
Where条件里拼接用户输入的字段名或表名,例如u.GetType().GetProperty(fieldName)+ 反射赋值后直接用于查询(这不等于参数化)
FromSqlRaw / ExecuteSqlRaw 必须手动参数化
当你不得不写原生 SQL(比如复杂报表、全文检索、跨库 JOIN),用 FromSqlRaw 或 ExecuteSqlRaw 时,它们本身不自动参数化。传入的字符串如果含用户输入,且没处理,立刻回归高危模式。
正确做法只有一种:用 @p0 占位符 + 额外参数对象,EF Core 会把它当参数传,不是字符串插值:
var users = _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Status = {0} AND CreatedAt > @minDate", "Active")
.AddParameter("minDate", DateTime.UtcNow.AddDays(-7))
.ToList();
- 占位符必须是
{0},{1}这类索引格式,或命名参数@paramName(需配合AddParameter) -
AddParameter是 EF Core 5+ 引入的安全补丁,旧版只能靠SqlParameter数组传参 - 绝对不要写
FromSqlRaw($"SELECT ... WHERE Name = '{name}'")—— 插值发生在 C# 层,SQL 还没到数据库就已污染
动态查询条件别硬拼 Where 表达式树
常见需求如“按姓名/邮箱/手机号模糊查”,有人会拼 Expression.AndAlso 树再编译成 Func,但这只是内存过滤;若想下推到数据库,仍要走 IQueryable 构建。问题在于:字段名(如 "Email")若来自用户输入,直接反射取属性就等于开放列名注入。
安全解法是白名单校验 + 显式分支:
var query = _context.Users.AsQueryable();
if (!string.IsNullOrWhiteSpace(searchField) && !string.IsNullOrWhiteSpace(searchValue))
{
switch (searchField.ToLowerInvariant())
{
case "name":
query = query.Where(u => u.Name.Contains(searchValue));
break;
case "email":
query = query.Where(u => u.Email.Contains(searchValue));
break;
default:
throw new ArgumentException("Invalid search field");
}
}
- 字段名必须限制在预设集合内,不能直接
GetProperty(searchField) - 值(
searchValue)始终走表达式参数,EF Core 自动处理 - IN 查询多个值?用
Contains方法(query.Where(u => ids.Contains(u.Id))),EF Core 会生成WHERE Id IN (@p0, @p1, ...)
别让 FromSqlRaw 成为参数化盲区
很多团队以为“用了 EF Core 就万事大吉”,结果在报表接口里大量用 FromSqlRaw,却把参数当字符串拼进去。最典型的错误是:
// ❌ 危险!字符串插值先发生,攻击载荷已嵌入 SQL
_context.FromSqlRaw($"SELECT * FROM Logs WHERE Level = '{level}'");
<p>// ✅ 正确:占位符 + AddParameter
_context.FromSqlRaw("SELECT * FROM Logs WHERE Level = @level")
.AddParameter("level", level);
另一个易忽略点:存储过程调用。即使过程本身参数化,若你在 C# 里用 FromSqlRaw($"EXEC LogSearch '{keyword}'"),照样中招。应该统一走 SqlParameter 数组传参,或改用 ExecuteSqlInterpolated(EF Core 5+,支持 $ 插值但底层强制参数化):
_context.Database.ExecuteSqlInterpolated($"EXEC LogSearch {keyword}");
注意:ExecuteSqlInterpolated 只认 {variable} 形式,且变量必须是简单类型或可被 EF Core 映射的类型;不能是表达式或对象实例。

















