EF Core使用生成查询存疑:存储过程调用报重复键异常及性能疑问
问题原因与解决方案
为什么调用存储过程会报错?
EF Core 使用 Include 查询时,内部会自动处理关联查询产生的重复行,把重复的实体(比如同一个 Promoter)合并到对象树中,不会重复添加到上下文的跟踪集合里。但用 FromSqlRaw 调用存储过程时,EF 会把返回的扁平化结果集逐行映射为实体对象,当结果集中出现相同主键的实体(比如同一个 Promoter 在多行结果里重复出现),EF 尝试将这些重复实体添加到跟踪集合时,就会触发「重复键已存在」的异常——因为上下文不允许同时跟踪两个主键相同的实体。
而 EF 自己生成的查询,底层会做结果集的去重合并处理,所以不会出现这个问题。
这么做能获得性能提升吗?
不一定,得看具体场景:
- 如果只是把 EF 自动生成的查询直接复制到存储过程里,性能基本没差别,甚至可能因为存储过程的参数嗅探、编译缓存问题导致性能下降。
- 如果你的
Include链太长,EF 生成的查询产生了大量笛卡尔积(结果集行数爆炸),这时候手动优化存储过程的查询逻辑(比如用更高效的关联方式、分页返回必要数据),可能会有性能提升。 - 但 EF Core 本身对多表关联查询有优化(比如 EF Core 5+ 的拆分查询
SplitQuery),合理使用的话,性能未必比存储过程差。
解决报错的方法
最直接的办法是关闭实体跟踪(如果只是读取数据不需要后续修改):
List<Application> Applications = await dbContext.Applications .FromSqlRaw("Exec GetFullApplication {0}", uuid) .AsNoTracking() // 关闭跟踪,避免重复键校验 .ToListAsync();
如果必须保留跟踪,那需要修改存储过程的返回逻辑,确保每个实体只返回一次,但这对于多表关联查询来说很难实现——因为关联必然会产生重复行,所以关闭跟踪是更可行的方案。
内容的提问来源于stack exchange,提问作者Florent
相关产品推荐
相关产品推荐

