从EF Core 3.1.4迁移至EF Core 5.0.3后,FromSqlInterpolated()与IQueryable<string>的使用问题求助
嘿,这个问题我之前升级版本时也碰到过,其实是EF Core 5.0对FromSql系列方法的行为做了更严格的规范,和3.1的宽松处理逻辑不一样~
问题根源
EF Core 5.0开始会严格校验你通过FromSqlInterpolated/FromSqlRaw执行的SQL是否属于可组合SQL。而存储过程的SQL语句通常是不可组合的——简单来说就是EF没法把存储过程的调用逻辑和你后续的Select操作拼接成一个有效的数据库查询语句。
在EF Core 3.1里,框架可能默认帮你把后续的Select切换到客户端内存中执行了,没抛出异常;但5.0把这个行为明确化,要求你显式决定查询的执行位置,避免潜在的性能浪费或者意外的查询行为。
解决方案
方案1:显式切换到客户端执行(最直接)
正如你发现的,用AsEnumerable()把查询从数据库端切换到客户端内存中处理,之后的Select操作就不会触发数据库端的SQL组合校验了。调整后的代码可以这样写:
public virtual IEnumerable<string> PR_HLP_GetHelpMessage(string lang, string controllerName) { var coll = this.Set<HelpMessageModel>().FromSqlInterpolated($"ProcedureName {lang}, {controllerName}"); // 先转成客户端枚举,再执行Select逻辑 return coll.AsEnumerable().Select(x => x.Message); }
如果业务场景确实需要返回IQueryable<string>,可以再加个AsQueryable(),不过此时的IQueryable已经是内存中的查询对象,后续LINQ操作也都会在客户端执行:
public virtual IQueryable<string> PR_HLP_GetHelpMessage(string lang, string controllerName) { var coll = this.Set<HelpMessageModel>().FromSqlInterpolated($"ProcedureName {lang}, {controllerName}"); return coll.AsEnumerable().Select(x => x.Message).AsQueryable(); }
方案2:修改数据库对象为可组合类型(进阶优化)
如果你的场景对性能要求很高,不想在客户端处理大量数据,可以考虑把存储过程改成表值函数。表值函数的SQL是可组合的,EF Core 5.0可以把它和后续的Select拼接成一个完整的数据库查询,所有操作都在数据库端完成。不过这个方案需要修改数据库中的对象,成本相对高一些。
额外优化建议
使用AsEnumerable()时要注意存储过程返回的数据量,如果数据过多会增加客户端内存压力。最优的方式是直接修改存储过程,让它只返回你需要的Message字段,这样既能减少传输到客户端的数据量,代码里也不需要额外的Select操作。
内容的提问来源于stack exchange,提问作者CatCode91

