ef core执行原生sql不会自动丢失变更跟踪:fromsqlraw返回实体并受跟踪,executesqlraw仅返回影响行数且不参与跟踪;但若fromsqlraw查询缺主键字段会导致实体为空或默认值,进而savechanges无效。

EFCore执行原生SQL会丢失变更跟踪吗
不会自动丢失,但取决于你用的是 FromSqlRaw 还是 ExecuteSqlRaw。前者返回实体,仍受上下文跟踪;后者只返回影响行数,不加载任何实体,自然也不进变更跟踪器。
常见错误现象:用 FromSqlRaw 查询后修改对象,调用 SaveChanges 却没生效——其实是查询时漏了主键字段,导致 EF 无法实例化完整实体,返回 null 或部分字段为默认值。
-
FromSqlRaw必须映射到有完整主键定义的实体类型(或带[Key]的 DTO),否则结果被忽略 - 若只需查一两个字段,别硬塞进实体类,改用
ExecuteSqlRaw+ 自定义DbDataReader解析,更稳 - SQL 中别写
SELECT *,EF 不认列顺序变化,字段名必须和属性名(或[Column]指定名)严格一致
参数化防注入该用 @p0 还是 @name
EF Core 3.0+ 强制要求命名参数(如 @name),位置参数(@p0)只在内部生成 SQL 时出现,你手写时不能用。用错会直接抛 InvalidOperationException:“The parameter name '@p0' was not defined in the SQL batch.”
使用场景:拼接动态 WHERE 条件、传入列表(需配合 IN 子句展开)。
- 字符串参数直接传入,EF 自动处理引号和转义:
context.Users.FromSqlRaw("WHERE Name = {0}", name)是错的,得用WHERE Name = @name+new SqlParameter("@name", name) - 传
List<int></int>给IN?EF 不支持直接传集合,得手动展开成逗号分隔参数,比如@id0, @id1, @id2,再逐个添加SqlParameter - 参数名大小写敏感,
@Name和@name是两个参数,别混用
执行 UPDATE/DELETE 为什么 SaveChanges 不触发
因为 ExecuteSqlRaw 是绕过 EF 变更跟踪的直连操作,它发完 SQL 就返回影响行数,上下文完全不知道数据变了。后续查出来的数据可能还是旧的,除非你清掉本地缓存或重新查询。
性能影响明显:高频执行原生 DML 后又读同一张表,容易读到脏数据(非事务隔离问题,是 EF 缓存没刷新)。
- 如果必须保证一致性,执行完
ExecuteSqlRaw立即调用context.ChangeTracker.Clear(),但代价是丢掉所有未提交的变更 - 更稳妥的做法:把这类操作封装进事务,用
context.Database.BeginTransaction()包住原生 SQL 和后续SaveChanges,并确保读操作在事务内重查 - 别在同一个上下文中混用原生 DML 和 EF 导航属性更新——EF 不会感知外力改动,关联对象状态极易错乱
DbContext 被释放后还能用 FromSqlRaw 吗
不能。一旦 DbContext 被 Dispose(比如用 using 块结束),其内部 DbConnection 关闭,再调用 FromSqlRaw 会立刻抛 ObjectDisposedException:“Cannot access a disposed context instance.”
容易踩的坑:异步方法里忘了 await,或者把 IQueryable 提前传出作用域,以为“只是个查询”,其实它绑定了已释放的上下文。
- 所有
FromSqlRaw返回的是IQueryable<t></t>,不是立即执行——真正执行在ToList/First等地方,那时上下文可能早已没了 - 不要把
FromSqlRaw结果赋给类字段或静态变量,除非你明确管理上下文生命周期 - 在 Web API 中,依赖注入的
DbContext默认是 Scoped,跨 Action 或跨 Task 使用大概率翻车
最常被忽略的一点:原生 SQL 的事务边界和 EF 的 SaveChanges 不自动对齐,哪怕都在一个 DbContext 实例里。想靠 EF 的事务包装原生 SQL,得显式开事务,而不是指望 SaveChanges 帮你兜底。











