fromsqlraw 不防注入,须用参数化({0}或sqlparameter);仅支持dbset,不可链式linq;列名/顺序/类型须与实体严格一致;建议加asnotracking()提升读性能。

FromSqlRaw 本身不防注入,必须配合参数化写法才安全;直接拼接字符串等于裸奔。
FromSqlRaw 必须用 {0} 占位符或 SqlParameter
EF Core 的 FromSqlRaw 不会自动识别变量,它只把字符串原样传给数据库——除非你显式提供参数。错误写法:"SELECT * FROM Users WHERE Name = '" + name + "'",这会触发 SQL 注入。正确做法是用位置占位符 {0} 或 SqlParameter:
context.Users.FromSqlRaw("SELECT * FROM Users WHERE Age > {0} AND Status = {1}", 18, "Active")context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name LIKE @name", new SqlParameter("@name", "%john%"))
前者靠 EF Core 自动转为命名参数(如 @p0),后者可控制 SqlDbType 和空值处理,更稳妥。
FromSqlRaw 只能用于 DbSet,不能链式调用 Where 等 LINQ 方法
你不能在 FromSqlRaw 后再写 .Where(x => x.IsActive),EF Core 不会把它翻译成 SQL,而是先执行原始 SQL,再在内存里过滤——数据量大时性能崩盘。常见误用:
context.Users
.FromSqlRaw("SELECT * FROM Users")
.Where(u => u.CreatedAt > DateTime.Today) // ❌ 内存过滤!
.ToList();
正确方式是把条件写进 SQL 里:
context.Users
.FromSqlRaw("SELECT * FROM Users WHERE CreatedAt > {0}", DateTime.Today)
.ToList();
或者改用 FromSqlInterpolated(EF Core 5+),它支持插值且自动参数化,写起来更自然:
var cutoff = DateTime.Today;
context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE CreatedAt > {cutoff}").ToList();
列名、顺序、类型必须和实体完全一致
FromSqlRaw 不做字段映射,也不容错。SQL 返回的列名必须和实体属性名严格匹配(大小写敏感,尤其 PostgreSQL);少一列,未映射属性为默认值;多一列,直接抛 InvalidOperationException;类型不兼容(比如数据库返回 datetime2,实体用 DateTime? 却写了 NOT NULL 列),也会失败。
- 别用
SELECT *:表结构变更后极易出错 - 别乱加别名:
SELECT u.Name AS UserName→ 实体必须有UserName属性,否则映射失败 - 计算列或
NULL值要小心:例如SELECT Id, Name, COUNT(*) AS Count,若实体没Count属性或类型不匹配,就挂
需要部分字段?新建一个轻量 DTO 类,用 AsNoTracking() + 手动投影,或者换 SqlQueryRaw<t>()</t>(EF Core 5+)。
查询完记得加 AsNoTracking()
FromSqlRaw 默认开启变更跟踪,哪怕你只是读个报表数据。这会导致 EF Core 缓存所有实体、监听属性变化、额外分配内存——纯读场景下毫无必要。
加上 AsNoTracking() 能显著降低 GC 压力和查询耗时:
context.Users
.FromSqlRaw("SELECT Id, Name, Email FROM Users WHERE IsActive = {0}", true)
.AsNoTracking()
.ToList();
注意:加了之后实体不可直接修改提交,如需更新,得重新查或手动 Attach。
最常被忽略的是列名大小写和空值处理——尤其跨数据库迁移时,PostgreSQL 默认小写,SQL Server 默认不敏感但配置可变;而 NULL 字段映射到非可空类型会静默失败或报异常,不是所有环境都开详细日志。











