65.9K
CodeProject 正在变化。 阅读更多。
Home

数据访问应用块 .NET 2.0:获取返回值、输出值

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (13投票s)

2006 年 9 月 22 日

CPOL

10分钟阅读

viewsIcon

164458

downloadIcon

3104

在 .NET 2.0 中使用 SqlHelper 并获取返回值和输出值。

SqlHelper 的一个变体...如果您使用 SqlHelper,必读

文章的第一部分将向您展示如何从 ExecuteDataSet 获取返回值。文章的第二部分将向您展示如何从 ExecuteNonQuery 获取返回值和输出值。

本文将向您展示如何使用 Microsoft 应用程序块并能够获取您的存储过程的返回值和输出参数。您会在网上找到许多关于调用存储过程并使用 ADO.NET 获取返回值和输出值的文章...但是如果您想使用 SqlHelper,那么您就无法获取返回值了。

我将深入 SqlHelper 类的代码,并解释我们如何修改代码(只需一点点,不要跑开)以从我们的存储过程中获取我们珍贵的返回值。

Sqlhelper 类的小回顾

SqlHelper 有四个主要功能。让我们回顾一下它们

  • ExecuteDataset 从 SQL 查询生成 DataSet
  • ExecuteReader 从 SQL 查询生成 SqlDataReader
  • ExecuteScalar 从 SQL 查询生成单个值对象。
  • ExecuteNonQuery 运行不带返回值的 SQL 查询(我们很快就会改变这一点)。

ExecuteDatasetExecuteDataset 将运行您的基本 SELECT 查询并生成一个 DataSet,然后可以将其绑定到服务器对象或用于创建 DataView。与所有方法一样,有许多重载。

ExecuteReaderExecuteReader 也用于 SELECT 语句,但通常保留用于性能真正重要的场景。SqlDataReader 就像 ADO classic 中的只进、只读记录集。它们适用于填充 ListBoxCheckBoxList

ExecuteScalarExecuteScalar 方法有多种用途,例如返回只有一个值的 SELECT 查询,例如 COUNT。但最常见的用法是运行一个返回新行 ID 的 INSERT 语句。这是 Transact SQL 中一个相当常见的技巧,但它需要在存储过程中进行 CAST 以确保以最易于 .NET 处理的格式返回结果行 ID。

INSERT (ColumnName1, ColumnName2) VALUES (@parameter1, @parameter2) 
SELECT CAST(@@Identity AS INTEGER)

开发人员被迫使用此方法将值从存储过程返回到您的业务类。以下是如果您想返回 Insert 的最后一个 ID 号,您必须如何做的示例。为了保持简单,该值以 Object 的形式返回到 .NET。要获取整数行 ID,请使用 Convert 语句。

int newRowId = Convert.ToInt32(SqlHelper.ExecuteScalar(connString,
 "usp_InsertStuffProcedure",
 parameter1,
 parameter2));

第一部分 - ExecuteDataset 详细示例

目标:从存储过程获取数据集,并使用 SqlHelper 中的 ExecuteDataset 获取存储过程的返回值。

好的,让我们来看一些代码

  • SqlProductProvider.cs 将扮演我们业务对象的角色。
  • SqlHelper.cs 将扮演我们数据层的角色(Microsoft 代码)。
  • default.aspx (default.cs) 将扮演 UI 层的角色。
  • web.config - 将保存我们的连接字符串。

这是我们存储过程的快照

SELECT
    ProductName
FROM
    Products
WHERE
     ProductName like @pProductName +'%'
     
     ....
     ....
     Return (1) -- This is the Return Value

UI (default.cs) 有一个按钮,用于获取 DataSet 对象以链接到 GridView(非常简单)。我们正在调用业务层以获取我们的 DataSet(业务层将与数据层通信以提供 DataSet 对象)。

// Pass the Letter "a" to get all products that start with the letter "a"
protected void Button2_Click(object sender, EventArgs e)
{
    GridView2.DataSource = sqlProductProvider.dsGetProductListsTest("a");
    GridView2.DataBind();
}

sqlProductProvider 具有此代码

public static DataSet dsGetProductListsTest(String owner)
{
    int ReturnValue;
    object[] objParams = { 0, owner };
    
    DataSet  ds = SqlHelper.ExecuteDataset(ConnectionString, "GetProjectLists", objParams);
    ReturnValue = (int)objParams[0];

    return ds;
}

这就是我要求 SqlHelper 给我 DataSet 的部分。请注意,有 9 个重载方法可以获取 DataSet,但在本文中,我将只关注一种方式。下图显示了我将使用的重载方法(9 种方法中的 6 种)。

让我们回顾一下我们必须为 ExecuteDataset 重载 #6 传递的参数

  • connectionString - SqlConnection 的有效连接字符串。
  • spName - 存储过程的名称。
  • parameterValues - 一个对象数组,用作存储过程的输入值(我们稍后讨论这个...)。
  • Return - 包含命令生成的结果集的 DataSet

让我们逐行回顾代码。

这里,我们应该有返回值

int ReturnValue;

这是我将发送给 ExecuteDataset 的参数数组。在索引 [0] 中,我的值为 0,第二个值是我传递给存储过程的字符串。索引 [0] 用于存储过程的返回值,索引 [1] 是存储过程的输入参数。

object[] objParams = { 0, owner };

调用 ExecuteDataset 并获取我的返回值

DataSet  ds = SqlHelper.ExecuteDataset(ConnectionString, "GetProjectLists", objParams);
ReturnValue = (int)objParams[0];

让我们回顾一下

  1. 有一个包含存储过程参数值的数组,其中索引 [0] 始终用于返回值。
  2. 我们执行方法,在 ExecuteDataset 期间,SqlHelper 类将返回给我们返回值(魔法?不,我们还有工作要做)。
  3. 将数组索引 [0] 赋值给我们的 int 变量。

仔细查看 Sqlhelper 地牢... 小心,非常小心(开玩笑...这很简单)

所有魔法都发生在 ExecuteDataset 部分。以下是获取返回值的全局分解,然后我将用调试器证明它

  1. 传递一个值数组,其中索引 0 用于返回值。我们称之为 ArrayA
  2. 调用 SqlHelperParameterCache.GetSpParameterSet,它返回一个 SQL 参数对象数组。我们称之为 ArrayB
  3. 此步骤只告诉我们存储过程有多少个参数(包括返回值),但我还没有在这些参数中赋值。
  4. 调用 AssignParameterValues 为我们的参数赋值。您将 ArrayA 值映射到 ArrayB 并赋值参数值。
  5. 调用 ExecuteDataset - 通过运行此方法,我们应该在 ArrayB 中获得更新的 ReturnValue 参数,而不是 ArrayA

这就是我向您展示如何获取返回值或您可能拥有的任何输出值的部分。我们将详细查看 ExecuteDataset(重载 #6),并向您展示如何在代码改动很小的情况下从存储过程获取返回值。首先,让我们全局地看一下 ExecuteDataset(重载 #6)方法。稍后我将逐行详细解释。

internal static DataSet ExecuteDataset(string connectionString, 
         string spName, params object[] parameterValues)
{
    DataSet dsReturn;
    if (connectionString == null || connectionString.Length == 0)
        throw new ArgumentNullException("connectionString");
    if (spName == null || spName.Length == 0)
        throw new ArgumentNullException("spName");

    // If we receive parameter values, we need to figure out where they go
    if ((parameterValues != null) && (parameterValues.Length > 0))
    {
        // Pull the parameters for this stored procedure from the parameter
        // cache (or discover them & populate the cache)
        //Original code from sqlHelper
        //SqlParameter[] commandParameters = 
        //     SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);
        // Added Parameter true to support ReturnValues
        SqlParameter[] commandParameters =
        //    SqlHelperParameterCache.GetSpParameterSet(connectionString, spName,true ); 

        // Assign the provided values to these parameters based on parameter order
        AssignParameterValues(commandParameters, parameterValues);

        // Call the overload that takes an array of SqlParameters
        //return ExecuteDataset(connectionString, 
        //       CommandType.StoredProcedure, spName, commandParameters);
        //Modify code - just store the dataset to dsReturn
        dsReturn = ExecuteDataset(connectionString, 
                    CommandType.StoredProcedure, spName, commandParameters);

        //Update the array -  parameterValues from the new CommandParameters
        //that should have the ReturnValue
        UpdateParameterValues(commandParameters, parameterValues);
    }
    else
    {
        // Otherwise we can just call the SP without params
        //return ExecuteDataset(connectionString, CommandType.StoredProcedure, spName);
        //Modify code
        dsReturn = ExecuteDataset(connectionString, CommandType.StoredProcedure, spName);
    }
    //Modify code
    return dsReturn;
}

代码很简单。从参数缓存中提取此存储过程的参数(或发现它们并填充缓存)。

// Pull the parameters for this stored procedure from
// the parameter cache (or discover them & populate the cache)
//Original code from sqlHelper
//SqlParameter[] commandParameters =
//   SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);
// Added Parameter true to support ReturnValues
SqlParameter[] commandParameters = 
  SqlHelperParameterCache.GetSpParameterSet(connectionString, spName,true);

我可以告诉 SqlHelperParameterCache.GetSpParameterSet 包含我的返回值(原始代码不支持 SqlHelperParameterCache.GetSpParameterSet 中的返回值)。请看下图以查看重载方法

调用后

// Added Parameter true to support ReturnValues
SqlParameter[] commandParameters = 
   SqlHelperParameterCache.GetSpParameterSet(connectionString, spName,true);

调试器显示 SqlHelper 找到了我的 ReturnValue 参数和 ProductName 参数,它们都没有值,这很正常。

调用后

// Assign the provided values to these parameters based on parameter order
AssignParameterValues(commandParameters, parameterValues);

我们可以看到我们从业务层传递的值(数组)存储在 SqlParameter[] commandParameters 中。这是代码,因此您无需向上滚动。

public static DataSet dsGetProductListsTest(String owner)
{
    int ReturnValue;
    //0 - for the RetunValue, owner is the string we passed
    // from the UI (have the value of "a") 
    object[] objParams = { 0, owner }; 

    DataSet  ds = SqlHelper.ExecuteDataset(ConnectionString, "GetProjectLists", objParams);
    ReturnValue = (int)objParams[0];

    return ds;
}

这是证明它的调试

调用后

// Call the overload that takes an array of SqlParameters
//return ExecuteDataset(connectionString, CommandType.StoredProcedure, 
//                      spName, commandParameters);

//Modify code - just store the dataset to dsReturn
dsReturn = ExecuteDataset(connectionString, CommandType.StoredProcedure, 
                          spName, commandParameters);

我们可以看到我们得到了 commandParameters 中的返回值

数组不具有与 CommandParameters 相同的值

到目前为止一切顺利。但是在 SqlHelper 中(深入数据层)的 CommandParameters 中拥有返回值对我们帮助不大。我们的目标是在业务层和 UI 层中获取返回值。为了做到这一点,我必须将 CommandParameters 数组中的值复制到我传入的数组中,并用返回值的更新索引 0。好吧,在这一点上,我必须编写一个执行此操作的小函数。

//Update the array -  parameterValues from
// the new CommandParameters that should have the ReturnValue
UpdateParameterValues(commandParameters, parameterValues);

这是方法。我将其放在 sqlHelper.cs 中,紧接着 AssignParameterValues 方法(您可以在下载示例中找到完整的代码)。

private static void UpdateParameterValues(
        SqlParameter[] commandParameters, object[] parameterValues)
{
    if ((commandParameters == null) || (parameterValues == null))
    {
        // Do nothing if we get no data
        return;
    }

    // We must have the same number of values
    // as we pave parameters to put them in
    {
        throw new ArgumentException("Parameter count does " + 
                  "not match Parameter Value count.");
    }

    // Iterate through the SqlParameters, assigning the values
    // from the corresponding position in the value array
    for (int i = 0, j = commandParameters.Length; i < j; i++)
    {
        //Update the Return Value
        if (commandParameters[i].Direction == ParameterDirection.ReturnValue)
        {
            parameterValues[i] = commandParameters[i].Value;
        }

        if (commandParameters[i].Direction == ParameterDirection.InputOutput)
            parameterValues[i] = commandParameters[i].Value;
    }
}

需要指出的一点是,我总是将返回值放在数组的索引 0 和值 0 中。这是在我的数组中给出返回值的代码

//Update the Return Value
if (commandParameters[i].Direction == ParameterDirection.ReturnValue)
{
    parameterValues[i] = commandParameters[i].Value;
}

对于输出参数的情况,我将它们放在输入参数之后数组的末尾。在此示例中,我没有使用任何输出参数。我稍后将在使用 ExecuteNonQuery 进行 InsertUpdate 时展示它。

\\Copy Input and Output Param to the Array 
if (commandParameters[i].Direction == ParameterDirection.InputOutput) 
    parameterValues[i] = commandParameters[i].Value;

让我们现在看一下调试器。在这里您可以看到 commandParameters 数组和 parameterValues 数组具有相同的值。最重要的是,还有返回值!

在 .NET 数组中是 ByRef 类型而不是 ByValue,这意味着我们的数组在业务层中得到了更新。现在,我将向您展示调用 SqlHelper 的业务层中的大图景。此方法位于我们的业务层中的 SqlProductProvider 类中。我将逐步证明我的数组得到了更新并且我拥有返回值。

public static DataSet dsGetProductListsTest(String owner)
{
    int ReturnValue;
    object[] objParams = { 0, owner };
    DataSet ds = SqlHelper.ExecuteDataset(ConnectionString, 
                           "GetProjectLists", objParams);
    ReturnValue = (int)objParams[0];
    return ds;
}

在运行 SqlHelper.ExecuteDataset 之前,我们的 Array[0] = 0(这是我给索引 0 的默认值)。

运行后

DataSet ds = SqlHelper.ExecuteDataset(ConnectionString, "GetProjectLists", objParams);

在这里您可以看到我得到了我的 ReturnValueobjParams[0] = 1,并且我的数组从 ExecuteDataset(我们的数据层)更新了。

第二部分 - ExecuteNonQuery 示例 - 获取返回值和输出值

SqlHelperExecuteNonQuery 执行相同的步骤;只是使用此方法,我们不会返回 DataSetDataReader。事实上,使用此方法获取返回值和输出值会更有意义。我们的存储过程执行一个非常简单的 Insert 并有一个名为 @pInsertStatus 的输出值,我们的返回值将给出新记录的 @@IDENTITY 值。本示例的目标是获取返回值和我们的新输出值。

Create PROCEDURE Products_SP_Insert
-- ****************************************************************************
-- PARAM
-- ****************************************************************************
@pProductName nvarchar(40), 
@pSupplierID int, 
@pCategoryID int, 
@pQuantityPerUnit nvarchar(20), 
@pUnitPrice money, 
@pUnitsInStock smallint, 
@pUnitsOnOrder smallint, 
@pReorderLevel smallint, 
@pDiscontinued bit,
@pInsertStatus varchar(50) output
AS
....

INSERT INTO 
    Products
    (
    ProductName ,
    SupplierID ,
    CategoryID ,
    QuantityPerUnit ,
    UnitPrice ,
    UnitsInStock ,
    UnitsOnOrder ,
    ReorderLevel ,
    Discontinued 
    )
    VALUES
    (
    @pProductName ,
    @pSupplierID ,
    @pCategoryID ,
    @pQuantityPerUnit ,
    @pUnitPrice ,
    @pUnitsInStock ,
    @pUnitsOnOrder ,
    @pReorderLevel ,
    @pDiscontinued 
    )

SET @intError = @@Error
SET 

-- Error validation
IF (@intError = 0) BEGIN
    SET @pInsertStatus = 'Insert Successfully'
    SET @intReturn = @@IDENTITY
END ELSE BEGIN
    SET @pInsertStatus = 'Insert Faild'
    SET @intReturn = 0
END
-- ****************************************************************************
-- RETURN
-- ****************************************************************************
SET NOCOUNT OFF
RETURN (@intReturn)

从 UI 中,我使用了这段代码。注意:所有输出参数都在数组的末尾,返回值在索引 0 处。

protected void Button3_Click(object sender, EventArgs e)
{
    // In a real world project it would be good to pass these values 
    // by Class object (or typed dataset) and give it to the Bussniess layer
    // and in the bussiness layer you can fill up the array, 
    //to make things simple I hard coded the values here in the UI

    object[] objParams = { 0,"ProductTest",1, 1, 
                           "48 - 6 oz jars",15.00,50,20,0,false,null};
    sqlProductProvider.InsertProduct(objParams);
}

这是业务层中的代码。我使用了与之前 ExecuteDataSet 相同的重载方法。

public static int InsertProduct(object[] objParams)
{
    int ReturnValue;
    string OutPutValue;

    ReturnValue = SqlHelper.ExecuteNonQuery(ConnectionString, "Products_SP_Insert", objParams);
    ReturnValue = (int)objParams[0];
    OutPutValue = (string)Params[10];

    return ReturnValue;
}

这是 SqlHelper.ExecuteNonQuery 方法

internal static int ExecuteNonQuery(string connectionString, 
         string spName, params object[] parameterValues)
{
    int intReturn;
    if (connectionString == null || connectionString.Length == 0)
        throw new ArgumentNullException("connectionString");
    if (spName == null || spName.Length == 0)
        throw new ArgumentNullException("spName");

    // If we receive parameter values, we need to figure out where they go
    if ((parameterValues != null) && (parameterValues.Length > 0))
    {
        // Pull the parameters for this stored procedure from
        // the parameter cache (or discover them & populate the cache)
        SqlParameter[] commandParameters = 
           SqlHelperParameterCache.GetSpParameterSet(connectionString, spName,true);

        // Assign the provided values to these parameters based on parameter order
        AssignParameterValues(commandParameters, parameterValues);

        // Call the overload that takes an array of SqlParameters
        //return ExecuteNonQuery(connectionString, 
        //         CommandType.StoredProcedure, spName, commandParameters); 
        intReturn = ExecuteNonQuery(connectionString, 
                    CommandType.StoredProcedure, spName, commandParameters);

        //Update the array -  parameterValues from the new CommandParameters
        //That should have the ReturnValue (I add this Method)

        UpdateParameterValues(commandParameters, parameterValues);
        return intReturn;
    }
    else
    {
        // Otherwise we can just call the SP without params
        return ExecuteNonQuery(connectionString, CommandType.StoredProcedure, spName);
    }
}

让我们逐行查看调试器并查看更改。这是我们的数组的视图,其中索引 0 是我们的返回值,索引 10 是我们的输出值,目前它们是空的。

在这里我们调用 GetSpParameterSet。注意:我将最后一个参数设置为 true 以在 SqlHelperParameterCache.GetSpParameterSet 中获取返回值。

// Pull the parameters for this stored procedure
// from the parameter cache (or discover them & populate the cache)
SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(
                                       connectionString, spName,true);

我们的调试显示

在这里我们调用 AssignParameterValues(将我们的数组链接到参数数组并复制值)

// Assign the provided values to these parameters based on parameter order
AssignParameterValues(commandParameters, parameterValues);

我这里只显示了索引 1,但它们都有值

调用 ExecuteNonQuery

//I changed this line to the line below
//return ExecuteNonQuery(connectionString,
//       CommandType.StoredProcedure, spName, commandParameters);
intReturn = ExecuteNonQuery(connectionString, CommandType.StoredProcedure, 
                            spName, commandParameters);

下面显示了索引 0 和索引 10(返回值和输出值)的调试。如您所见,我在 commandParameters 中有我的返回值和输出值,但仍然不在我的数组中。

调用 UpdateParameterValues

//Update the array -  parameterValues from the new CommandParameters
// that should have the Return Value (I add this Method)
UpdateParameterValues(commandParameters, parameterValues);

我们的调试显示

我们的数组已更新,索引 0 中包含返回值,索引 10 中包含输出值。完成所有这些操作后,我们的业务层将拥有返回值和输出值。

ReturnValue = SqlHelper.ExecuteNonQuery(ConnectionString, 
                        "Products_SP_Insert", objParams);
ReturnValue = (int)objParams[0];
OutPutValue = (string)Params[10];

并证明它

摘要

好了,我完成了!在这篇文章中,我向您展示了如何通过 3 到 4 行代码拥有一个非常干净的业务逻辑层,调用存储过程并获取返回值和输出值。您无需担心任何事情,只需按照正确的顺序将值传递给 SqlHelper

注意:我无法获取 ExecuteDataReader 的返回值,因为它保持与 SQL Server 的开放连接。如果您确实需要从“RecordSet”获取返回值,那么请使用 ExecuteDataSet 并使用 DataTable 对象。我包含了 SQLHelper 类的完整源代码(带有一些小的修改)。

反馈

请随意对本文留下任何反馈。希望您喜欢它。

© . All rights reserved.