Как сделать запрос с передачей нескольких параметров?

Добавил ListBox на WinForm (там изменил SelectionMode=MultiSimple). Для выбора несколько услуг. Сейчас визуальный выбор несколько услуг выбирается, но при выборе он суммирует только 1 услугу. И еще в данный момент есть ошибка если не выбрана ни одна из услуг (System.NullReferenceException: "Ссылка на объект не указывает на экземпляр объекта"), но это видимо из-за исключения ToString. Но самое важное, как правильно сделать суммирование всех выбранных услуг + цена 1 комнаты?

Нужно правильно суммировать 2 услуги сразу, а не только одну.

// Запрос к таблице услуг
private void GetAdditionalServices()
{
    Con.Open();
    SqlCommand cmd = new SqlCommand("SELECT* FROM AdditionalServicesTbl", Con);
    SqlDataReader rdr;
    rdr = cmd.ExecuteReader();
    DataTable dt = new DataTable();
    dt.Columns.Add("ServicesNum", typeof(int));
    dt.Load(rdr);
    listBox1.ValueMember = "ServicesNum";
    listBox1.DataSource = dt;
    Con.Close();
}

// Получаем счет за (услугу-тут проблема) + счет за комнату
int Price = 0;
int PriceServices = 0;

private void fetchCostServices()
{
    Con.Open();
    string Query = "SELECT ServicesCost FROM AdditionalServicesTbl where ServicesNum=" + listBox1.SelectedValue.ToString() + "";
    SqlCommand cmd = new SqlCommand(Query, Con);
    DataTable dt = new DataTable();
    SqlDataAdapter sda = new SqlDataAdapter(cmd);
    sda.Fill(dt);
    foreach (DataRow dr in dt.Rows)
    {
        PriceServices = CONVERT.ToInt32(dr["ServicesCost"].ToString());
    }
    Con.Close();
}

private void fetchCostRooms()
{
    Con.Open();
    string Query = "SELECT TypeCost FROM RoomTbl join TypeTbl on RType=TypeNum where RNum="
        + RoomCb.SelectedValue.ToString() + "";
    SqlCommand cmd = new SqlCommand(Query, Con);
    DataTable dt = new DataTable();
    SqlDataAdapter sda = new SqlDataAdapter(cmd);
    sda.Fill(dt);
    foreach (DataRow dr in dt.Rows)
    {
        Price = CONVERT.ToInt32(dr["TypeCost"].ToString());
    }
    Con.Close();
}

// Кол-во дней прожживание в гостинице (цена 1 комнаты + цена 1 услуги = общая сумма (цена комнаты + цена всех выбранных услуг)
private void DurationTb_TextChanged(object sender, EventArgs e)
{
    if (AmountTb.Text == "")
    {
        AmountTb.Text = " 0";
    }
    else
    {
        try
        {
            int Total = Price * CONVERT.ToInt32(DurationTb.Text) + PriceServices * CONVERT.ToInt32(DurationTb.Text);
            AmountTb.Text = "" + Total;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

Ответы (2 шт):

Автор решения: Vitaliy Zlobin

Нужно сделать метод, который даст тебе список всех услуг, вида:

'Услуга1', 'Услуга2' и т.д.

Затем изменить этот запрос:

SELECT ServicesCost FROM AdditionalServicesTbl where ServicesNum=" + listBox1.SelectedValue.ToString() + "

на:

SELECT SUM(ServicesCost) FROM AdditionalServicesTbl where servicesNum IN (" + ServicesList + ")";
→ Ссылка
Автор решения: Alexander Petrov

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

if (listBox1.SelectedItems.Count == 0)
    return;

Далее, нужно получить из листбокса выбранные значения и вставить их в sql-запрос.

string format = "SELECT ServicesCost FROM AdditionalServicesTbl WHERE ServicesNum in ({0})";
string values = string.Join(",", listBox1.SelectedItems.OfType<DataRowView>().Select(row => row[0]));
string query = string.Format(format, values);

К листбоксу прибинден дататейбл, поэтому в коллекции SelectedItems находятся DataRowView. Извлекаем их и берём значение первой (она же единственная) колонки (с индексом 0).

Если уж вы решили использовать DataTable, то знайте, что SqlDataAdapter способен сам открыть соединение, создать внутри себя SqlCommand и выполнить запрос.

var dt = new DataTable();
var sda = new SqlDataAdapter(query, connectionString);
sda.Fill(dt);

После чего суммируем полученные из БД данные:

foreach (DataRow row in dt.Rows)
{
    PriceServices += row.Field<int>("ServicesCost");
}

Обратите внимание на +=.

Полный код метода может выглядеть так:

private void FetchCostServices()
{
    if (listBox1.SelectedItems.Count == 0)
        return;

    string format = "SELECT ServicesCost FROM AdditionalServicesTbl WHERE ServicesNum in ({0})";
    string values = string.Join(",", listBox1.SelectedItems.OfType<DataRowView>().Select(row => row[0]));
    string query = string.Format(format, values);

    var dt = new DataTable();
    var sda = new SqlDataAdapter(query, connectionString);
    sda.Fill(dt);

    foreach (DataRow row in dt.Rows)
    {
        PriceServices += row.Field<int>("ServicesCost");
    }
}

Но если нужна только сумма, то её можно и нужно посчитать прямо в БД. Пример в своём ответе показал Vitaliy Zlobin.

Для получения одного значения из БД нет смысла использовать DataTable. Применим метод ExecuteScalar.

private void FetchCostServices()
{
    if (listBox1.SelectedItems.Count == 0)
        return;

    string format = "SELECT SUM(ServicesCost) FROM AdditionalServicesTbl WHERE ServicesNum in ({0})";
    string values = string.Join(",", listBox1.SelectedItems.OfType<DataRowView>().Select(row => row[0]));
    string query = string.Format(format, values);

    using (var con = new SqlConnection(connectionString))
    {
        con.Open();
        using (var cmd = new SqlCommand(query, con))
        {
            PriceServices = (int)cmd.ExecuteScalar();
        }
    }
}

Обратите внимание на применение using - это автоматически освободит ресурсы. Соединение при этом закроется тоже само, вызывать Close не нужно.
Не делайте SqlConnection полем класса. Создавайте его прямо по месту.


Я не знаю точные типы ваших данных, поэтому замените при необходимости int на правильные типы в вызовах OfType, Field и при касте.


Правильнее было бы сделать, чтобы метод возвращал значение: private int FetchCostServices(), вместо использования поля класса.

Ещё хорошо бы использовать асинхронность, но это отдельная тема. Задайте новый вопрос, если есть желание вникнуть.

→ Ссылка