LinQ中调用.FromSqlRaw()执行存储过程后链式使用.Join()实现服务端查询的问题
LinQ中调用.FromSqlRaw()执行存储过程后链式使用.Join()实现服务端查询的问题
这个问题我之前也碰到过,EF Core抛出的这个异常其实是在告诉你:默认情况下,直接用FromSqlRaw调用存储过程的SQL是非可组合的——EF Core没办法把你后续的Join逻辑和存储过程的SQL拼接成一个完整的数据库查询语句,所以只能让你转去客户端处理。但既然你需要服务端执行来保证性能,这几个方案可以试试:
方案一:把Join逻辑直接写到存储过程里
这是最直接高效的方式,既然存储过程本身就在数据库端执行,不如把后续要做的Join逻辑直接整合进存储过程里。比如原来的存储过程只返回TableA的部分数据,现在修改SP,让它在内部完成和目标表的Join操作,直接返回你最终需要的结果集。
这样你在LinQ里只需要调用存储过程就能拿到最终数据,完全不需要后续的链式操作,性能肯定是最优的,毕竟所有逻辑都在数据库端完成。
方案二:用可组合的SQL语法调用存储过程
有些数据库(比如SQL Server)支持把存储过程当成表来查询,也就是用SELECT * FROM 存储过程名()的形式。这种写法下,EF Core会认为这是可组合的SQL,就能把后续的Join逻辑拼接成完整的数据库查询语句,在服务端执行。
修改你的代码试试:
await myContext.TableA .FromSqlRaw("SELECT * FROM SP()") // 改成这种调用方式 .Join( myContext.TableA, spResult => new { a = spResult.PK }, tableA => new { a = tableA.PK }, (spResult, tableA) => new { spResult, tableA } ) .Select(x => x.spResult) .ToListAsync();
注意事项:
- 不是所有数据库都支持这种语法,得看你用的数据库类型;
- 存储过程返回的结果集字段必须和你用来映射的实体(这里是TableA)完全匹配,否则EF Core会映射失败;如果SP返回的是自定义字段,建议创建一个对应的DTO实体,然后在DbContext里把它注册为无键实体(用
modelBuilder.Entity<MyDto>().HasNoKey();)。
方案三:用表值函数(TVF)代替存储过程
如果你的存储过程逻辑可以转换成表值函数,那这会是更优雅的方案。EF Core对表值函数的支持更好,表值函数本身就是可组合的,你可以直接在LinQ里像操作普通表一样调用它,然后链式进行Join等操作,所有逻辑都会在服务端执行。
步骤大概是:
- 在数据库中创建一个表值函数,把原来存储过程的逻辑迁移进去;
- 在EF Core的DbContext里映射这个表值函数;
- 在LinQ查询中直接调用这个函数,再进行Join操作。
比如EF Core 3.0+可以直接用函数映射,写法类似:
var results = await myContext.MyTableValueFunction() .Join(myContext.TableA, ...) .Select(...) .ToListAsync();
备注:内容来源于stack exchange,提问作者pedral
相关产品推荐
相关产品推荐

