fromsqlraw仅支持以select开头的单条查询语句,必须映射到已定义实体且挂载于dbset,不支持链式linq、匿名类型或手拼sql;需参数化防注入,字段名与主键须严格匹配,并推荐加asnotracking提升只读性能。

FromSqlRaw 不是万能查询入口,它只接受 SELECT 语句、必须映射到已定义实体、且不能链式调用 LINQ 方法——写错就 silently 返回空或触发内存过滤。
FromSqlRaw 必须以 SELECT 开头,且只能用于 DbSet
EF Core 把 FromSqlRaw 当作“从数据库加载实体”的通道,所以 SQL 字符串必须以 SELECT 起始,否则会直接抛异常:InvalidOperationException: The given SQL must begin with 'SELECT'.
- 不能混入
UPDATE、INSERT或多个语句(如"SELECT ...; SELECT ...") - 只能挂在
DbSet<t></t>上,比如context.Users.FromSqlRaw(...),不能写成context.Database.FromSqlRaw(...) - 返回类型固定为该
DbSet对应的实体类(或其可映射基类/接口),不支持匿名对象或任意 DTO —— 想返回自定义结构,请改用Database.SqlQueryRaw<t>()</t>
参数化不是可选项,而是强制门槛
手拼字符串("WHERE Name = '" + name + "'")等于主动打开 SQL 注入大门。EF Core 不解析变量,只原样转发 SQL 字符串。
- 正确做法只有两种:
FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18)(位置占位符),或更稳妥的FromSqlRaw("SELECT * FROM Users WHERE Name = @name", new SqlParameter("@name", name)) -
FromSqlInterpolated更推荐:写成$"SELECT * FROM Users WHERE Status = {status}",EF Core 自动转为命名参数(如@p0),防注入零配置 - 别用
@p0手写参数名——EF Core 3.0+ 明确禁止你显式写@p0,只允许你自己定义的名字(如@status),否则报错:The parameter name '@p0' was not defined in the SQL batch.
字段名、主键、大小写,三者缺一不可
查出来全是 null 或默认值?大概率是列名没对上,或者漏了主键字段。
- SQL 返回的列名必须和实体属性名(或
[Column("xxx")]指定名)**完全一致,包括大小写**(PostgreSQL / 启用区分大小写的 SQL Server 尤其敏感) - 如果实体有主键(
[Key]或约定主键),SQL 中必须包含该字段,否则 EF Core 无法构造实体实例,结果集里对应行会变成null - 不要写
SELECT *—— 表结构变更后列序一变,映射就崩;显式列出字段,或用别名对齐:SELECT Id AS Id, Name AS Name FROM Users
AsNoTracking 是性能分水岭,不是可选优化
原生 SQL 默认仍启用变更跟踪,但多数只读场景根本不需要。不加 AsNoTracking(),大结果集下内存占用和 GC 压力会明显升高。
- 查完只展示、导出、统计?一律加上:
context.Users.FromSqlRaw(...).AsNoTracking().ToList() - 千万别在
FromSqlRaw后接.Where()或.OrderBy()—— EF Core 不翻译,而是把整张表查回来再内存过滤,数据量一过万就卡死 - 需要动态条件,要么重写进 SQL(如拼
AND Status = @status),要么老实用 LINQ,别硬套FromSqlRaw
最常被忽略的是:FromSqlRaw 返回的实体虽然受跟踪,但修改后调用 SaveChanges 并不会自动同步——因为 EF Core 不知道这些实体是从哪条 SQL 来的,状态管理是断开的。真要更新,得先 Attach 再设 State,或者直接换 ExecuteSqlRaw。











