Передать разделенный запятыми список как строку чисел для запроса в виде списка целых чисел для PostgreSQL db в C# с использованием Npgsql - PullRequest
1 голос
/ 07 апреля 2020

В моем проекте WinForms, написанном на C#, я подключаюсь к PostgreSQL базе данных, используя инфраструктуру Npg sql.

Я заполняю пару DGV с помощью DataTable из PG база данных. Я также пытаюсь реализовать функцию поиска, которая, по инициативе пользователя, ищет в таблице данных DGV строку поиска, а затем перестраивает DGV на основе этих результатов. Когда процесс выполняет поиск в DataTable, он формирует список идентификаторов записей (например, «1234,2345,3456»), которые будут переданы в запрос db для повторного заполнения DGV только теми записями, которые соответствуют поисковому значению пользователя.

Вот моя функция поиска, которая создает список идентификаторов и запрос базы данных:

private void btnSearch_Click(object sender, EventArgs e)
{
    string searchText = txtSearchCatsGrid.Text;
    string idToAddToList = "";
    string tempIdList = "";
    string idListForQuery = "";

    DataTable dt = ((DataTable)dgvCategories.DataSource);
    string cellValue;
    int rowIndex;
    int columnIndex;

    for (rowIndex = 0; rowIndex <= dt.Rows.Count - 1; rowIndex++)
    {
        for (columnIndex = 0; columnIndex <= dt.Columns.Count - 1; columnIndex++)
        {
            cellValue = dt.Rows[rowIndex][columnIndex].ToString();

            if (searchText == cellValue)
            {
                // Set corresponding dgvCategories cell's background color to yellow
                dgvCategories[columnIndex, rowIndex].Style.BackColor = Color.Yellow;

                idToAddToList = dt.Rows[rowIndex][0].ToString();
            }
        }

        if (!string.IsNullOrEmpty(idToAddToList) && !tempIdList.Contains(idToAddToList)) tempIdList += idToAddToList + ",";

    }

    if (!string.IsNullOrEmpty(tempIdList))
    {
        // Remove the trailing comma that was added the last time an id was added to the list
        idListForQuery = tempIdList.Remove(tempIdList.Length - 1, 1);
    }

    Console.WriteLine("idListForQuery: " + idListForQuery);

    // Refresh dgvCategories with just rows from search, using idListForQuery

    string companyFilter = cboSelectCompany.Text;
    string categoryFilter = cboSelectCategory.Text;

    db categoriesData = new db();

    if (categoryFilter == "All Categories")
    {
        string catsQuery = "SELECT id, category, source_company, old_value, old_desc, new_value, new_desc, reference1, reference2 " +
                            "FROM masterfiles.xref"+
                            " WHERE company_name = @company" +
                                " AND id IN (@idList)" +
                                //" AND id IN (SELECT UNNEST(@idList))" +
                            " ORDER BY category, old_value, source_company";
        this.dtCategories = categoriesData.GetDgvData(catsQuery, companyFilter, categoryFilter, idListForQuery);
    }
    else
    {
        string catsQuery = "SELECT id, category, source_company, old_value, old_desc, new_value, new_desc, reference1, reference2 " +
                            "FROM masterfiles.xref" +
                            " WHERE company_name = @company" +
                                " AND category = @category" +
                                " AND id IN (@idList)" +
                                //" AND id IN (SELECT UNNEST(@idList))" +
                            " ORDER BY old_value, source_company";
        this.dtCategories = categoriesData.GetDgvData(catsQuery, companyFilter, categoryFilter, idListForQuery);
    }

    dgvCategories.DataSource = this.dtCategories;

    if (dtCategories.Rows.Count == 0)
    {
        return;
    }
    else
    {
        dgvCategories.Columns[0].Visible = false;
        dgvCategories.Rows[0].Cells[0].Selected = false;
    }
}

Затем в моем классе базы данных функция GetDgvData () выглядит следующим образом:

public DataTable GetDgvData(string selectQuery, string companyFilter, string categoryFilter, string idFilter)
{
    using (NpgsqlConnection conn = new NpgsqlConnection(connString))
    using (NpgsqlCommand cmd = new NpgsqlCommand(selectQuery, conn))
    {
        cmd.Parameters.Add(new NpgsqlParameter("company", companyFilter));

        if (!string.IsNullOrEmpty(idFilter)) cmd.Parameters.Add(new NpgsqlParameter("idList", idFilter);
        //if (!string.IsNullOrEmpty(idFilter)) cmd.Parameters.Add("idList", NpgsqlDbType.Array | NpgsqlDbType.Integer).Value = idFilter;
        //if (!string.IsNullOrEmpty(idFilter)) cmd.Parameters.Add(new NpgsqlParameter("idList", "ARRAY[" + idFilter + "]"));

        if (categoryFilter != "All Categories") cmd.Parameters.Add(new NpgsqlParameter("category", categoryFilter));

        conn.Open();

        DataSet ds = new DataSet();

        using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd))
        {
            da.Fill(ds);
        }

        return ds.Tables[0];
    }
}

У меня проблемы с использованием этого списка идентификаторов в качестве параметра в запросе.

Вы заметите, что у меня есть несколько строк, закомментированных в обоих блоках кода. Это различные попытки передачи списка, все из которых приводят к ошибке в строке da.Fill(ds);

Когда я просто использую незакомментированную строку в коде построения запроса, AND id IN (@idList) и «обычный» метод При добавлении параметра if (!string.IsNullOrEmpty(idFilter)) cmd.Parameters.Add(new NpgsqlParameter("idList", idFilter);, также комментируемого в функции GetDgvData(), ошибка в da.Fill(ds); представляет собой NpgsqlPostgresException, «оператор не существует: целое число = текст».

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

После еще одного исследования я нашел пример кода, который преобразует тип Array db в Integer db Type, который является первой строкой. закомментированного кода в разделе параметров в функции GetDgvData(). Когда я попытался это сделать, было выдано исключение: «System.InvalidCastException:« Не могу записать тип System.String в виде массива System.Int32 ».

После еще одного исследования я попробовал третий комментарий строка в функции GetDgvData(), которая окружает параметр "Array[]". Исключение, которое я получаю за это: «оператор не существует: целое число = текст». Я попробовал это с и без функции UNNEST() в запросе. Без UNNEST() я получаю Npg sql .PostgresException, «оператор не существует: целое число = текст». Используя UNNEST(), я получаю PostgresException, «функция unnest (text) не существует»).

Я также создал идентификаторы как List<> и попытался передать его в GetDgvData(), но я не передать его правильно или что-то, потому что я получаю исключение нулевой ссылки в GetDgvData(), когда он попадает в строку, где я пытаюсь что-то сделать с List<> - в том числе просто пытаюсь посмотреть содержимое в консоли.

Так что я действительно не знаю, что еще попробовать отсюда.

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

1 Ответ

0 голосов
/ 09 апреля 2020

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

Похоже, вы пытались использовать массив, который абсолютно подходит для go. Проблема в том, что вы использовали IN, который предназначен для дискретных списков. Для массива вам нужно использовать ANY.

select *
from foo
where bar in (1, 2, 3)

переводится в

select *
from foo
where bar = any (array[1, 2, 3]) -- or shorthand any ('{1,2,3}')

Во-вторых, если вы не используете Dynami c SQL, если вы вызываете переменную связывания, вам необходимо присвоить ей значение. Обратное неверно. Поэтому, как только вы объявите @category в своем SQL, вы ДОЛЖНЫ присвоить ему значение в C#. Один из возможных способов обойти это - заключить C# logi c в SQL - я не верю, что вы столкнетесь с какой-либо потерей производительности для этого.

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

SELECT
  id, category, source_company, old_value, old_desc, new_value, new_desc, 
  reference1, reference2 
FROM masterfiles.xref
WHERE
  company_name = @company
  AND (category = @category or @category = 'All Categories')
  AND id = any (@idList)
ORDER BY old_value, source_company

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

public DataTable GetDgvData(string selectQuery, string companyFilter, 
    string categoryFilter, int[] idFilter)
{
    using (NpgsqlConnection conn = new NpgsqlConnection(connString))
    {
        using (NpgsqlCommand cmd = new NpgsqlCommand(selectQuery, conn))
        {
            cmd.Parameters.AddWithValue("company", NpgsqlTypes.NpgsqlDbType.Varchar,
                companyFilter);
            cmd.Parameters.AddWithValue("category", NpgsqlTypes.NpgsqlDbType.Varchar,
                categoryFilter);
            cmd.Parameters.AddWithValue("idList", NpgsqlTypes.NpgsqlDbType.Array |
                NpgsqlTypes.NpgsqlDbType.Integer, idFilter);

Остальной код такой же.

Простите за использование AddWithValue, которое, как я знаю, неодобрительно во многих кругах ... Откровенно, я люблю это и чувствую аргументы против этого не перевешивают его полезность, особенно с PostgreSQL.

...