Member 14633063 Ответов: 1

Почему я не могу получить значение выходного параметра обратно из executenonquery?


Я выполняю хранимую процедуру и пытаюсь получить значение выходного параметра "после выполнения" (@parmRecID), которое должно быть значением RecID записи, добавленной хранимой процедурой в таблицу.

Значение выходного параметра всегда равно "1", хотя хранимая процедура правильно устанавливает его в значение RecID вновь добавленной записи.

Приведенный ниже код создает экземпляр класса, затем использует функцию из этого класса для выполнения хранимой процедуры, а затем (безуспешно) извлекает значение выходного параметра и возвращает это значение в качестве возвращаемого значения вызываемого метода (функции).

SQLDatabase MySQLClass = new SQLDatabase();
string TheDatabase = "CustomerDB";
string TheProcedure = "usp_AddCustomer";

Dictionary<string, ParameterValues> TheParams = new Dictionary<string, ParameterValues>();
ParameterValues values = new ParameterValues();
values.Value = TxtFirstName.Text; values.Direction = "Input";
TheParams.Add("@parmFirstName", values);
values = new ParameterValues();
values.Value = TxtLastName.Text; values.Direction = "Input";
TheParams.Add("@parmLastName", values);
values = new ParameterValues();
values.Value = TxtAddress.Text; values.Direction = "Input";
TheParams.Add("@parmAddress", values);
values = new ParameterValues();
values.Value = TxtCity.Text; values.Direction = "Input";
TheParams.Add("@parmCity", values);
values = new ParameterValues();
values.Value = TxtZipCode.Text; values.Direction = "Input";
TheParams.Add("@parmZipCode", values);
values = new ParameterValues();
values.Value = ""; values.Direction = "Output";
TheParams.Add("@parmRecID", values);

string RecID = MySQLClass.ExecStoredProcedure(TheDatabase, TheProcedure, TheParams);

MessageBox.Show("From FrmMain, RecID is " + RecID);


Ниже приведен вызываемый метод (MySQLClass.ExecStoredProcedure):

public string ExecStoredProcedure(string Database, string Procedure, Dictionary<string, ParameterValues> Parameters)
        {
            string TheConnectionString = $"Data Source=DESKTOP-EL4GU86;Initial Catalog={Database};Integrated Security=true";
            using (SqlConnection Conn = new SqlConnection(TheConnectionString))
            {
                using (SqlCommand Cmd = new SqlCommand(Procedure, Conn))
                {
                    Conn.Open();
                    Cmd.CommandType = CommandType.StoredProcedure;
                    foreach (KeyValuePair<string, ParameterValues> entry in Parameters)
                    {
                        ParameterValues vals = entry.Value;
                        Cmd.Parameters.AddWithValue(entry.Key, vals.Value);
                        switch (vals.Direction)
                        { 
                            case "Output":
                                Cmd.Parameters[entry.Key].Direction = ParameterDirection.Output;
                                break;
                            default:
                                Cmd.Parameters[entry.Key].Direction = ParameterDirection.Input;
                                break;
                        }
                    }

                    Cmd.ExecuteNonQuery();

                    foreach (SqlParameter param in Cmd.Parameters)      // This 'foreach' is just to show the 'after execution'
                    {                                                   // value of the output parameter..
                        string pName = param.ParameterName;
                        if (pName == "@parmRecID")
                        {
                            string pValue = param.Value.ToString();
                            MessageBox.Show("From SQLClass, " + pName + " = " + pValue);
                        }
                    }

                    return Cmd.Parameters["@parmRecID"].Value.ToString();
                }
            }
        }
    }

    public class ParameterValues
    {
        public string Value { get; set; }
        public string Direction { get; set; }
    }


Исследуя эту проблему, я нашел "System.Data.ParameterDirection.Аргумент returnvalue;"
это позволило бы мне получить возвращаемое значение из хранимой процедуры, но я только сейчас пытаюсь понять, почему я не могу получить значение выходного параметра после выполнения хранимой процедуры...

Пока я этим занимаюсь, вот хранимая процедура...

CREATE OR ALTER procedure [dbo].[usp_AddCustomer] 

	@parmFirstName as varchar(50),
	@parmLastName as varchar(50),
	@parmAddress as varchar(50),
	@parmCity as varchar(50),
	@parmZipCode as varchar(50),
	@parmRecID as varchar(10) output

as

	if exists
				(
					select	* 
					from	CustomerDB.dbo.SmallCustomerTable 
					where	FirstName = @parmFirstName
					and		LastName = @parmLastName
					and		Address = @parmAddress
				)
			set @parmRecID = '0'
	else
			begin
				insert into	CustomerDB.dbo.SmallCustomerTable
				select		@parmFirstName,
							@parmLastName,
							@parmAddress,
							@parmCity,
							@parmZipCode 
				set @parmRecID = (select convert(varchar, max(RecID)) from SmallCustomerTable)
			end

GO


Что я уже пробовал:

Приведенный выше код-это то, что я пробовал...

1 Ответов

Рейтинг:
12

Richard Deeming

Потому что значение вашего параметра установлено в пустую строку, и вы не устанавливаете значение этого параметра. Size свойство, параметр по умолчанию равен nvarchar(1).

В результате ваш код будет возвращать только первый символ значения выходного параметра.

Быстрое решение состоит в том, чтобы установить Size = -1 о выходном параметре.

case "Output":
    Cmd.Parameters[entry.Key].Direction = ParameterDirection.Output;
    Cmd.Parameters[entry.Key].Size = -1;
    break;
Есть различные другие улучшения, которые вы могли бы сделать. Например, ваш код в настоящее время будет работать только в том случае, если у вас есть вызываемый параметр @parmRecID, и он будет возвращать только значение этого параметра. Любые другие выходные параметры будут проигнорированы.

Вы также должны использовать SCOPE_IDENTITY в вашей хранимой процедуре, а не при выборе максимального идентификатора.
SCOPE_IDENTITY (Transact-SQL) - SQL Server | Microsoft Docs[^]

Вот вам и начало:
public class ParameterValue
{
    public object Value { get; set; }
    public string Direction { get; set; } = "Input";
    public int? Size { get; set; }
}

public class SQLDatabase
{
    private readonly string _connectionString;
    
    public SQLDatabase(string connectionString)
    {
        _connectionString = connectionString;
    }
    
    public SQLDatabase() : this("Data Source=DESKTOP-EL4GU86;Integrated Security=true")
    {
    }
    
    public int ExecuteNonQuery(string database, string commandText, IReadOnlyDictionary<string, ParameterValue> parameters, CommandType commandType = CommandType.StoredProcedure)
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            connection.ChangeDatabase(database);
            
            using (var command = new SqlCommand(commandText, connection))
            {
                command.CommandType = commandType;
                
                var outputParameters = new Dictionary<SqlParameter, ParameterValue>();
                foreach (var pair in parameters)
                {
                    var param = command.Parameters.AddWithValue(pair.Key, pair.Value.Value);
                    if (pair.Value.Size != null) param.Size = pair.Value.Size.GetValueOrDefault();
                    
                    switch (pair.Value.Direction)
                    {
                        case "Output":
                        {
                            param.Direction = ParameterDirection.Output;
                            outputParameters.Add(param, pair.Value);
                            break;
                        }
                        case "ReturnValue":
                        {
                            param.Direction = ParameterDirection.ReturnValue;
                            outputParameters.Add(param, pair.Value);
                            break;
                        }
                    }
                }
                
                int result = command.ExecuteNonQuery();
                
                foreach (var pair in outputParameters)
                {
                    pair.Value.Value = pair.Key.Value;
                }
                
                return result;
            }
        }
    }
}
Использование:
var db = new SQLDatabase();
const string theDatabase = "CustomerDB";
const string commandText = "usp_AddCustomer";

var parameters = new Dictionary<string, ParameterValue>
{
    ["@paramFirstName"] = new ParameterValue { Value = TxtFirstName.Text, Size = 50 },
    ["@paramLastName"] = new ParameterValue { Value = TxtLastName.Text, Size = 50 },
    ["@paramAddress"] = new ParameterValue { Value = TxtAddress.Text, Size = 50 },
    ["@paramCity"] = new ParameterValue { Value = TxtCity.Text, Size = 50 },
    ["@paramZipCode"] = new ParameterValue { Value = TxtZipCode.Text, Size = 50 },
    ["@paramRecID"] = new ParameterValue { Direction = "Output", Size = -1 }
};

db.ExecuteNonQuery(theDatabase, commandText, parameters);

object RecID = parameters["@paramRecID"].Value;
MessageBox.Show("From FrmMain, RecID is " + RecID);
Хранимая процедура:
CREATE OR ALTER PROCEDURE [dbo].[usp_AddCustomer] 
(
    @parmFirstName As varchar(50),
    @parmLastName As varchar(50),
    @parmAddress As varchar(50),
    @parmCity As varchar(50),
    @parmZipCode As varchar(50),
    @parmRecID As int OUTPUT
)
As
    If Exists
    (
        SELECT * 
        FROM dbo.SmallCustomerTable 
        WHERE FirstName = @parmFirstName
        And LastName = @parmLastName
        And Address = @parmAddress
    )
    BEGIN
        SET @paramRecID = 0;
    END
    Else
    BEGIN
        INSERT INTO dbo.SmallCustomerTable (FirstName, LastName, Address, City, ZipCode)
        VALUES (@parmFirstName, @parmLastName, @parmAddress, @parmCity, @parmZipCode);
        
        SET @paramRecID = SCOPE_IDENTITY();
    END;
GO


Member 14633063

Спасибо Ричард
Ранее я установил параметр = "xxxx" (пытаясь заставить его работать) перед выполнением, но это не сработало. Я, используя ваши комментарии как момент "ага!", изменил направление на InputOutput, после чего он распознал размер "xxxx" и вернул все значение вместо только первого символа.
Так что теперь это работает. Я тоже собираюсь посмотреть .Размер и другие предложения по улучшению, которые вы сделали.

Спасибо снова...