执行存储过程时,使用CommandType.StoredProcedure相对于使用CommandType.Text有什么好处?

20

在C#中,要使用存储过程,我会有以下这样的代码(省略了连接代码):

 string sql = "GetClientDefaults";

 SqlCommand cmd = new SqlCommand(sql);
 cmd.CommandType = CommandType.StoredProcedure;    //<-- DO I NEED THIS??
 cmd.Parameters.AddWithValue("@computerName", computerName);

sql是存储过程的名称。现在,这段代码似乎可以正常工作,即使没有注释掉的这一行。

那么我需要这行吗?设置它是否有性能(或其他)好处?不设置它或将其设置为Text是否有好处?

4个回答

21
根据这篇博客文章中的测试,当你使用CommandType.Text时,SQL Server将为你执行参数化操作,即通过在语句外部包装sp_executesql。但是,当你使用CommandType.StoredProcedure时,你需要自己进行参数化操作,以减轻数据库的工作量。后一种方法更快。 编辑: 设置 我已经进行了一些测试,以下是结果。
创建此存储过程:
create procedure dbo.Test
(
   @Text1 varchar(10) = 'Default1'
  ,@Text2 varchar(10) = 'Default2'
)
as
begin
   select @Text1 as Text1, @Text2 as Text2
end

使用SQL Server Profiler为其添加跟踪。

然后使用以下代码进行调用:

using System;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main()
        {
            CallProcedure( CommandType.Text );
            CallProcedure( CommandType.StoredProcedure );
        }

        private static void CallProcedure(CommandType commandType)
        {
            using ( SqlConnection connection = new SqlConnection("Data Source=localhost;Initial Catalog=Test;Integrated Security=SSPI;") )
            {
                connection.Open();
                using ( SqlCommand textCommand = new SqlCommand("dbo.Test", connection) )
                {
                    textCommand.CommandType = commandType;
                    textCommand.Parameters.AddWithValue("@Text1", "Text1");
                    textCommand.Parameters.AddWithValue("@Text2", "Text2");
                    using ( IDataReader reader = textCommand.ExecuteReader() )
                    {
                        while ( reader.Read() )
                        {
                            Console.WriteLine(reader["Text1"] + " " + reader["Text2"]);
                        }
                    }
                }
            }
        }
    }
}

结果

两种情况下都使用RPC进行调用。

下面是使用CommandType.Text时跟踪的结果:

exec sp_executesql N'dbo.Test',N'@Text1 nvarchar(5),@Text2 nvarchar(5)',@Text1=N'Text1',@Text2=N'Text2'

接下来是使用 CommandType.StoredProcedure 的结果:

exec dbo.Test @Text1=N'Text1',@Text2=N'Text2'

可以看到,文本调用被包装在一个 sp_executesql 的调用中,以便正确地进行参数化。当然,这样做会产生一些额外的开销,因此我之前说使用 CommandType.StoredProcedure 会更快速的说法仍然成立。

另一个值得注意的事情,也是一个关键问题,就是当我创建没有默认值的过程时,出现了以下错误:

Msg 201,级别16,状态4,过程测试,行0 过程或函数 'Test' 需要参数 '@Text1',但未提供该参数。

造成这种情况的原因是调用 sp_executesql 的方式,在这里可以看到参数被声明和初始化,但是它们并没有被使用。为了使调用起作用,它应该像这样:

exec sp_executesql N'dbo.Test @Text1, @Text2',N'@Text1 nvarchar(5),@Text2 nvarchar(5)',@Text1=N'Text1',@Text2=N'Text2'

这意味着,当你使用 CommandType.Text 时,除非你希望始终使用默认值,否则必须将参数添加到 CommandText 中。

因此,回答你的问题:

  1. 使用 CommandType.StoredProcedure 更快。
  2. 如果你使用 CommandType.Text,则必须在调用过程时添加参数名称,除非你希望使用默认值。

那么使用存储过程可能更快吗? - MAW74656
1
@MAW74656 是的。还要注意Panagiotis Kanavos的答案,除了SQL Server之外,可能还有其他提供程序不理解您正在尝试执行存储过程,除非您指定它。 - Andreas Ågren
我理解其他供应商的观点,只是这个应用程序不太可能需要那种改变。还有许多商业企业应用程序需要使用SQL Server(任何特定的数据库服务器),我敢打赌它们在那里也不使用提供程序工厂。 - MAW74656
这里有大量出色的工作!我唯一剩下的问题是没有参数的存储过程之间的区别。当有参数时,我使用StoredProcedure并将参数添加到Command对象的参数集合中。 - MAW74656
@MAW74656 我所能看到的调用无参数过程的唯一区别就是使用CommandType.StoredProcedure会稍微更快。 - Andreas Ågren

10

实际上有一个很大的区别。如果你指定了命令类型StoredProcedure,那么你添加到SqlCommand的任何参数都将成为存储过程调用中的一个参数。如果你将其保留为Text,那么这些参数将会被添加到批处理中,而不是存储过程。为了说明这一点,让我们创建一个虚拟的存储过程:

create procedure usp_test 
    @p1 char(10)  = 'foo',
    @p2 int = 42
as
    select @p1, @p2;    
go

然后编译这个简短的C#应用程序:

   static void Main(string[] args)
    {
        ExecWithType(CommandType.Text);
        ExecWithType(CommandType.StoredProcedure);
    }

    static void ExecWithType(CommandType type)
    {
        using (SqlConnection conn = new SqlConnection(Settings.Default.connString))
        {
            conn.Open();
            using (SqlCommand cmd1 = new SqlCommand("usp_test", conn))
            {
                cmd1.CommandType = type;
                cmd1.Parameters.AddWithValue("@p1", "bar");
                cmd1.Parameters.AddWithValue("@p2", 24);
                using (SqlDataReader rdr = cmd1.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        Console.WriteLine("Type: {0} Result: @p1: {1} @p2: {2}", type, rdr[0], rdr[1]);
                    }
                }
            }
        }
    }

结果是:

Type: Text Result: @p1: foo        @p2: 42
Type: StoredProcedure Result: @p1: bar        @p2: 24

出错了! 对于 CommandType.Text 设置,尽管参数已经传递给了批处理,但是它们没有传递给存储过程。这导致了很多小时的调试乐趣...


2
因此,使用参数时,没有灰色地带,CommandType.StoredProcedure 明显更好、更准确、更快。 - MAW74656

7

您需要将此设置为允许ADO.NET帮助您。当您使用CommandType.StoredProcedure时,您只需将CommandText设置为存储过程的名称。

例如,这样做:

YourSqlCommand.CommandType = CommandType.StoredProcedure;
YourSqlCommand.CommandText = "dbo.YourStoredProc";

等同于:

YourSqlCommand.CommandText = "exec dbo.YourStoredProc";

我在任何版本中都没有使用“Exec”或“dbo”,但它仍然可以正常工作。这样做是否还有其他帮助的方法? - MAW74656
我会重新编译它...调试并真正测试一下..因为我还没有看到过这样的工作方式,而不必设置CommandType...嗯嗯嗯??? - MethodMan
4
在SQL Server中,如果存储过程是批处理的第一条语句,你不必输入“exec”。 - user596075
1
@Shark - 好的,这解释了那部分,但是设置commandtype的唯一原因是什么?只是为了使语法更长吗?肯定有一些“好处”。 - MAW74656
1
@MAW74656 如果是使用无参数的查询或存储过程,使用不同的CommandType可能没有实现上的差异。我建议运行SQL Profiler并查看它们在数据库中的情况,但我敢打赌它们看起来会相同。 - user596075
显示剩余5条评论

3

CommandType不仅适用于SQL Server,它是IDbCommand接口的一个属性,指示底层提供程序以特定方式处理CommandText。虽然SQL Server可能将单词名称视为存储过程,但您不应该期望其他提供程序能够正常工作。

通常情况下,您应该优先使用由提供程序生成的类,如DbCommand,而不是特定的类,如SqlCommand。这样,您只需在配置文件中更改提供程序字符串即可针对不同的数据库进行操作。


1
@PagagiotisKanavos - 我认为你在这里打了一场不同的战斗。大多数在线示例代码使用SQLCommand。但即使我接受这个前提,你仍然没有说出设置CommandType会产生什么影响。 - MAW74656
没有什么需要争论的。你使用特定的类,就会被绑定到提供程序并且必须重写所有内容。示例代码不是生产代码。至于CommandType的作用-你认为只需传递名称并假设默认的CommandType.Text就可以在Oracle中执行存储过程吗?无论如何,在生产代码中,我永远不会信任意外和未记录的行为,只是为了避免设置一个值。 - Panagiotis Kanavos
1
但是设置它会做什么? - MAW74656

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接