直接拼接字符串进FromSqlRaw存在SQL注入风险,必须用SqlParameter传参;参数名须与SQL中@xxx完全一致;查询非实体类型需用FromSqlInterpolated、DbDataReader或VIEW;避免FromSqlRaw.AsEnumerable()导致性能问题。

为什么直接拼接字符串进 FromSqlRaw 是危险的
因为 SQL 注入风险真实存在:用户输入若未经处理就拼进 FromSqlRaw 的 SQL 字符串里,攻击者可能闭合单引号、注入 UNION SELECT 或执行 DROP TABLE。EF Core 不会自动转义字符串参数——它只信任你传进去的完整 SQL 字符串。
用 SqlParameter 替代字符串插值
必须把所有外部输入(如用户 ID、搜索关键词)作为参数传入,而不是用 $"SELECT * FROM Users WHERE Id = {id}" 这类写法。EF Core 支持原生 ADO.NET 参数语法(@paramName),且能正确绑定 SqlParameter 实例。
常见错误现象:InvalidOperationException: The required column 'xxx' was not present in the results of a raw SQL query. —— 通常是因为参数名拼错或没传参,导致 SQL 执行失败后返回空结果集。
- 使用命名参数(
@id、@name),不要用位置占位符({0}) - 参数名必须和 SQL 中的
@xxx完全一致(区分大小写) - 用
new SqlParameter("@id", userId)构造,不要用字符串拼接 - 如果参数是可空类型(如
int?),传null时需显式指定DBNull.Value或让 EF Core 自动处理(.NET 8 + EF Core 8 默认支持)
示例:
var userId = 123;
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Id = @id AND Status = @status",
new SqlParameter("@id", userId),
new SqlParameter("@status", "Active"))
.ToList();查询结果映射到非实体类型时要小心
FromSqlRaw 默认只支持映射到已知的 DbSet<t></t> 类型(即上下文里注册过的实体)。如果想查自定义结构(比如聚合统计),不能直接用 FromSqlRaw + 匿名类型或 DTO。
使用场景:做报表统计、跨表聚合、调用存储过程返回非标准结构。
- 方案一:用
FromSqlInterpolated+ 实体类(推荐),确保返回字段名与实体属性名/列名匹配 - 方案二:改用
Database.GetDbConnection()手动开连接 +ExecuteReader,自己读取DbDataReader并映射(绕过 EF Core 映射层,但失去变更跟踪) - 方案三:在数据库中建一个
VIEW,然后为它创建对应实体并加入ModelBuilder
容易踩的坑:FromSqlRaw(...).AsEnumerable() 看似能后续用 LINQ,但实际是把全部结果拉到内存再过滤——性能崩盘,且无法利用数据库索引。
.NET 8 + EF Core 8 的兼容性注意点
EF Core 8 对 FromSqlRaw 的参数绑定更严格:不支持混合使用字符串插值和参数(比如 FromSqlRaw($"SELECT * FROM Users WHERE Id = {id}") 会编译通过但运行时报错或被静默忽略参数)。
- 确认项目引用的是
Microsoft.EntityFrameworkCore.SqlServer(或其他对应 provider)8.x 版本,低版本可能缺少对DateTimeOffset或JsonDocument参数的正确序列化支持 - SQL Server 用户注意:
datetime2字段传参时,用DateTime即可;但若数据库列是datetime(旧版),传DateTimeKind.Unspecified可能引发精度截断 - 日志里看到
Microsoft.EntityFrameworkCore.Database.Command日志级别为Debug时,能看到最终执行的带参数的 SQL —— 这是验证参数是否生效的最直接方式
复杂点往往藏在参数类型和数据库列类型的隐式转换里,比如把 string 当 Guid 传、把 int 当 bigint 用,这些不会报错,但可能触发全表扫描或意外截断。

















