Member 13863605 Ответов: 3

Отправка нескольких параметров из ASP.NET к моей хранимой процедуре в SQL server


У меня есть окно со списком внутри Asp.Net оттуда пользователь выбирает один или несколько параметров и отправляет их в хранимую процедуру. Выбор количества параметров полностью зависит от пользователя, поэтому я не знаю, сколько параметров пользователь собирается выбрать из списка. Я также хочу получить данные обратно из таблицы с этими параметрами, когда я нажимаю на кнопку отправки и отображаю их в gridview. Проблема, с которой я сталкиваюсь, заключается в том, что я могу отправить один параметр и получить данные обратно из моей хранимой процедуры, но я действительно не знаю, как отправить несколько параметров из списка в мою хранимую процедуру.

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

Below is the code for single parameter in Asp.Net

protected void Button_Click(object sender, EventArgs e)
{
    string s = "Submit";
    SqlCommand cmd = new SqlCommand(s, con);

    cmd.CommandType = CommandType.StoredProcedure;

    cmd.Parameters.Add("@Name", SqlDbType.VarChar).Value = lbCT.SelectedItem.Value;

    con.Open();

    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);

    gvDS.DataSource = ds; 
    gvDS.DataBind();

    con.Close();
}
Below is my stored procedure in SQL Server

USE [Database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [Submit]
@Name varchar(12)
as 
begin

    select *
    from Employee 
    where Name = @Name
end

Richard Deeming

Хранимая процедура не принимает несколько значений. Вы планируете переписать его, или вы просто пытаетесь вызвать его несколько раз?

3 Ответов

Рейтинг:
1

Vincent Maverick Durano

Я не нахожусь на своей машине разработки, поэтому не могу написать образец с использованием хранимых процедур. Но вот один из них использует динамический запрос:

private string GenerateDynamicQuery(string baseSql, StringCollection sc){
	StringBuilder sb = new StringBuilder(string.Empty);

	foreach (string item in sc){	
		sb.AppendFormat("{0}('{1}'); ", baseSql, item);
	}
		
	return sb.ToString();
}
	
private void InsertRecords(StringCollection sc){
	
	const string sqlStatement = "INSERT INTO Employee (Name) VALUES";
        string dynamicQuery = GenerateDynamicQuery(sqlStatement,sc);
		
        using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
           using(SqlCommand cmd = new SqlCommand(dynamicQuery,connection)){
                cmd.CommandType = CommandType.Text;
                cmd.ExecuteNonQuery();
        }
			
	//bind GridView after
	BindGrid(sc);	

}
		
private void BindGrid(StringCollection sc){
	StringBuilder sb = new StringBuilder(string.Empty);
	foreach (string item in sc){	
	        sb.AppendFormat("'{0}',", item);
	}

	string param = sb.ToString().TrimEnd(',');
	string sqlStatement = string.Format("SELECT * FROM Employee WHERE Name IN ({0});",param);
		
    	using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
        	using(SqlCommand cmd = new SqlCommand(sqlStatement,connection)){

                DataTable dt = new DataTable();
                SqlDataAdapter ad = new SqlDataAdapter(cmd);
                ad.Fill(dt);

                if (dt.Rows.Count > 0) { //check if the query returns any data
                       GridView1.DataSource = dt;
                       GridView1.DataBind();
                }
                else
                {
                     //No records found
                }
        }

    }
}
		
protected void Button1_Click(object sender, EventArgs e)
{
        StringCollection sc = new StringCollection();
	foreach (ListItem item in ListBox1.Items)
	{
		if (item.Selected){
			sc.Add(item.Text);
		}
	}

       InsertRecords(sc);
    }


Вот еще одно решение с параметризацией запроса для "предотвращения" SQL-инъекции:

private void InsertRecords(StringCollection sc){
	const string sqlStatement = "INSERT INTO Employee (Name) VALUES (@param1)";
   
	foreach (string item in sc){	
        	using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
           		using(SqlCommand cmd = new SqlCommand(sqlStatement ,connection)){
               	 		cmd.CommandType = CommandType.Text;
                		cmd.Parameters.AddWithValue("@param1", item)
                		cmd.ExecuteNonQuery();
        		}
		}
        }
			
	//bind GridView after
	BindGrid(sc);	

}
		
private void BindGrid(StringCollection sc){
	string[] strArray = new string[sc.Count];
	sc.CopyTo(strArray,0);

	var parms = strArray.Select((s, i) => "@param1" + i.ToString()).ToArray();
  	var inclause = string.Join(",", parms);

	string sqlStatement = "SELECT * FROM Employee WHERE Name IN ({0})";
		
    	using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
        	using(SqlCommand cmd = new SqlCommand(string.Format(sqlStatement, inclause),connection)){
 		
    			for (var i = 0; i < valuearray.Length; i++)
    			{
      				cmd.Parameters.AddWithValue(parms[i], strArray[i]);
    			}
                	DataTable dt = new DataTable();
                	SqlDataAdapter ad = new SqlDataAdapter(cmd);
                	ad.Fill(dt);

                	if (dt.Rows.Count > 0) { //check if the query returns any data
                       		GridView1.DataSource = dt;
                       		GridView1.DataBind();
                	}
                	else
                	{
                     	//No records found
                	}
        	}

    	}
}

protected void Button1_Click(object sender, EventArgs e)
{
        StringCollection sc = new StringCollection();
	foreach (ListItem item in ListBox1.Items)
	{
		if (item.Selected){
			sc.Add(item.Text);
		}
	}

       InsertRecords(sc);
 }


PS:никогда не тестировался на решении paramertize на самом деле, но это должно дать вам некоторое представление о том, как это сделать.


Vincent Maverick Durano

Я знаю о SQL-инъекции. на самом деле я написал об этом статью здесь: https://www.codeproject.com/Articles/1105224/Protect-Your-Data-Prevent-SQL-Injection

Причина, по которой я дал это решение, заключается в том, что:

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

Я был бы признателен, если бы вы могли предоставить пользователю "решение" вместо того, чтобы сразу же проголосовать за мое решение.

Richard Deeming

Хранимые процедуры не имеют отношения к SQLi. Вполне возможно написать код без хранимых процедур, который не является уязвимым; и так же возможно написать код с использованием хранимых процедур, которые не являются уязвимыми. является уязвимый.

И нет никакой гарантии, что пользователь не имеет никакого контроля над элементами списка. Даже если проверка ViewState включена и работает должным образом, может существовать другой экран, на котором пользователи смогут управлять элементами, что приведет к сохраненной уязвимости SQLi.

Vincent Maverick Durano

"И нет никакой гарантии, что пользователь не имеет никакого контроля над элементами списка."

точка зрения принята. Но поскольку элементы из ListBox используются в качестве параметров для запроса, я предположил, что элементы поступают из базы данных. Опять же, даже если он жестко закодирован, элементы являются постоянными, а не входными данными от пользователя.

"там может быть другой экран, где пользователи могут управлять элементами"

Может, ты и прав. но мне любопытно, каким образом?

Richard Deeming

У вас может быть один экран, где пользователи могут вводить элементы, который использует правильно параметризованные запросы.

Пользователь вводит элемент со значением Robert');DROP TABLE Employee;--, который правильно хранится в базе данных.

На следующем экране пользователь выбирает элемент списка со значением Robert');DROP TABLE Employee;--, и нажимает на кнопку.

БУМ. Ваш динамический запрос пытается выполнить DROP TABLE команда. У вас есть постоянная уязвимость SQLi.

Vincent Maverick Durano

Точка зрения принята. Я обновил свое решение, чтобы добавить запрос paramterize. Теперь доволен? ;)

Richard Deeming

Так - то лучше! :)

NB: Тебе это не нужно. GenerateDynamicQuery позвоните в полицию. InsertRecords метод уже не тот.

Vincent Maverick Durano

Я идиотка! Спасибо, что заметили! Обновленный ;)

Richard Deeming

Вопрос не совсем ясен, но я думаю, что рассмотрел обе возможные интерпретации в решении 2. :)

Рейтинг:
1

Member 12893295

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


CHill60

Абсолютно неправильный способ сделать это

Рейтинг:
0

Richard Deeming

Лучший способ передать несколько значений одному параметру хранимой процедуры-это использовать табличный параметр:
Возвращающие Табличное Значение Параметры | Майкрософт Документы[^]

CREATE TYPE dbo.StringListTable As Table
(
    Value varchar(50) NOT NULL
);
GO
CREATE PROCEDURE dbo.ListEmployees
(
    @Names dbo.StringListTable READONLY
)
As
BEGIN
    SELECT
        *
    FROM
        Employee As e
    WHERE
        Exists
        (
            SELECT 1
            FROM @Names As n
            WHERE n.Value = e.Name
        )
    ORDER BY
        Name
    ;
END
Использование:
var names = new DataTable();
names.Columns.Add("Value", typeof(string));

foreach (ListItem item in lbCT.Items)
{
    if (item.Selected)
    {
        names.Rows.Add(item.Value);
    }
}

using (var connection = new SqlConnection("... YOUR CONNECTION STRING HERE ..."))
using (var command = new SqlCommand("dbo.ListEmployees", connection))
{
    command.CommandType = CommandType.StoredProcedure;
    var pNames = command.Parameters.AddWithValue("@Names", names);
    pNames.SqlDbType = SqlDbType.Structured;
    pNames.TypeName = "dbo.StringListTable";
    
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);

    gvDS.DataSource = ds; 
    gvDS.DataBind();
}


Кроме того, для простого списка строк можно передать разделенный запятыми список значений и использовать функция SPLIT_STRING[^] (SQL 2016), или пользовательская функция разделения строк[^] если вы используете более старую версию, чтобы разделить значения обратно в табличную переменную или временную таблицу.

А еще есть подход XML[^], но производительность, как правило, довольно плохая.



С другой стороны, если вы хотите передать несколько параметров текстовому запросу, вам нужно будет построить правильно параметризованный запрос:
using (var connection = new SqlConnection("... YOUR CONNECTION STRING HERE ..."))
using (var command = new SqlCommand("", connection))
{
    int index = 0;
    var sb = new StringBuilder("SELECT * FROM Employee");
    foreach (ListItem item in lbCT.Items)
    {
        if (item.Selected)
        {
            string name = "@p" + index;
            command.Parameters.AddWithValue(name, item.Value);
            sb.Append(index == 0 ? " WHERE Name In (" : ", ");
            sb.Append(name);
            index++;
        }
    }
    
    if (index != 0)
    {
        sb.Append(")");
    }
    
    sb.Append(" ORDER BY Name;");
    cmd.CommandText = sb.ToString();
    
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);

    gvDS.DataSource = ds; 
    gvDS.DataBind();
}


Vincent Maverick Durano

Лучше, чем у меня. Только одна вещь, хотя это не имеет прямого отношения к вопросу, и это не имеет большого значения - когда вы имеете дело с одной таблицей, используйте DataTable вместо DataSet. :)

Richard Deeming

Действительно - это то, что я обычно делаю. Но ... DataSet бит просто скопирован с вопроса. :)

Vincent Maverick Durano

НП. Еще я дал бы тебе 5!