SQL Server将SP_EXECUTESQL识别为对象而不是过程名称
我正在使用DBContext.Database.SqlQuery
从我的C#代码存储库执行存储过程。
它工作正常,但我想知道它为什么执行如下所示的过程:
exec sp_executesql N'EXEC GetCaseList @CaseStage',N'@CaseStage int',@CaseStage=9
而不是
EXEC GetCaseList @CaseStage = 9
有没有办法让我的所有程序都像这样从c#执行
EXEC GetCaseList @CaseStage = 9
而不是exec sp_executesql N'EXEC GetCaseList @CaseStage',N'@CaseStage int',@CaseStage=9
?
如何使SQL Server Profiler将过程名称视为对象而不是SP_EXECUTESQL?
注意:我想从c#执行过程作为EXEC GetCaseList @CaseStage = 9
因为我通过SQL Server Profiler以表格格式保存跟踪数据。 在ObjectName列中,它将sp_executesql显示为对象而不是过程名称(GetCaseList)作为对象。 我只能从c#代码进行更改。
问题是大多数EF执行的数据库调用都使用带有CommadType
Text
DbCommand
,因此尽管SqlServer识别SP调用,但它通过sp_executesql
它们作为文本执行。
要获得所需的行为,应该以这种方式设置命令:
DbCommand command = ...; command.CommandText = "StoredProcedureName"; command.CommandType = CommadType.StoredProcedure;
不幸的是,EF没有提供指定命令类型的标准方法。 我建议的解决方案基于:
- 自定义SP使用
CallPrefix StoredProcedureName
调用SQL语法,以便不干扰常规调用 - EF命令拦截在执行命令之前删除前缀并更改命令类型。
这是实施:
using System.Data; using System.Data.Common; using System.Data.Entity.Infrastructure.Interception; public static class Sp { public const string CallPrefix = "CallSP "; public static string Call(string name) { return CallPrefix + name; } public class CallInterceptor : DbCommandInterceptor { public static void Install() { DbInterception.Remove(Instance); DbInterception.Add(Instance); } public static readonly CallInterceptor Instance = new CallInterceptor(); private CallInterceptor() { } static void Process(DbCommand command) { if (command.CommandType == CommandType.Text && command.CommandText.StartsWith(Sp.CallPrefix)) { command.CommandText = command.CommandText.Substring(Sp.CallPrefix.Length); command.CommandType = CommandType.StoredProcedure; } } public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { Process(command); base.ReaderExecuting(command, interceptionContext); } } }
您只需要将上面的类添加到项目中,调用Sp.CallInterceptor.Install()
一次,例如在DbContext
静态构造函数中:
public class YourDbContext : DbContext { static YourDbContext() { Sp.CallInterceptor.Install(); } // ... }
然后像这样更改你的SP调用(使用你的样本):
从:
return DataContext.Database.SqlQuery("EXEC GetCaseList @CaseStage", new SqlParameter("@CaseStage", paramList.CaseStageID)).ToList();
至:
return DataContext.Database.SqlQuery(Sp.Call("GetCaseList"), new SqlParameter("@CaseStage", paramList.CaseStageID)).ToList();
将生成(对于paramList.CaseStageID == 9
):
EXEC GetCaseList @CaseStage = 9
在entity framework/ ADO.net中使用sp_executesql是故意的。据观察,有时在生成的sql中,EF在直接执行查询和有时使用sp_executesql之间起决定性作用。当有客户端时,sp_executesql会起作用。参数化有助于重用一个参数化编译计划。 如果没有指定参数,则SQL Server会尝试执行自动参数化,以帮助重用查询计划。
似乎使用sp_executesql或直接sql批处理的决定由ADO.Net的SQLCommand对象控制。 根据表格数据流(TDS),只有两种方法可以执行SQL查询 – 使用RPC执行SQL存储过程并使用SQL Batch for T-SQL。 因此,当我们有一个参数化查询时,我们倾向于使用RPC并调用sp_executesql.More来了解查询执行模式 。
有关查询参数化的更多信息,请参见
原因如下:
主要原因(1): TSQL字符串只构建一次,之后每次使用sp_executesql调用相同的查询时,SQL Server从缓存中检索查询计划并重用它。
(2): sp_executesql允许对语句进行参数化,因此在SQL注入方面它比EXEC更安全。
比较示例请查看以下链接: EXEC和EXEC sp_executesql的比较
我认为这是因为EF需要动态生成SQL命令,所以需要使用exec sp_executesql
来动态执行sql。