简体   繁体   English

将逗号分隔的列表作为数字字符串传递,以使用 Npgsql 作为 C# 中 PostgreSQL db 的整数列表进行查询

[英]Pass comma-separated list as string of numbers to query as list of integers for PostgreSQL db in C# using Npgsql

In my WinForms project written in C# I'm connecting to a PostgreSQL database using the Npgsql framework.在我用 C# 编写的 WinForms 项目中,我正在使用 Npgsql 框架连接到 PostgreSQL 数据库。

I'm populating a couple of DGVs with a DataTable from the PG database.我正在使用 PG 数据库中的 DataTable 填充几个 DGV。 I'm also trying to implement a search feature that, when initiated by the user, searches the DGV's DataTable for the search string and then rebuilds the DGV based on those results.我还尝试实现一个搜索功能,当用户启动该功能时,它会在 DGV 的 DataTable 中搜索搜索字符串,然后根据这些结果重建 DGV。 As the process searches the DataTable, it's building a list of record IDs (ex, "1234,2345,3456") that will be passed to the db query to repopulate the DGV with just the records that match the user's search value.当进程搜索 DataTable 时,它正在构建一个记录 ID 列表(例如,“1234,2345,3456”),该列表将传递给 db 查询,以仅使用与用户搜索值匹配的记录重新填充 DGV。

Here's my search function that builds the list of IDs and the db query:这是构建 ID 列表和数据库查询的搜索 function:

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;
    }
}

Then in my db class, the GetDgvData() function looks like this:然后在我的数据库 class 中,GetDgvData() function 看起来像这样:

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];
    }
}

I'm having trouble with using that list of IDs as a parameter in the query.我在使用该 ID 列表作为查询中的参数时遇到了问题。

You'll notice that I have several lines commented out in both code blocks.您会注意到我在两个代码块中都注释掉了几行。 Those are various attempts at passing the list, all of which generate an error at the line da.Fill(ds);这些是传递列表的各种尝试,所有这些都在da.Fill(ds);行产生错误。

When I just use the uncommented line in the query building code, AND id IN (@idList) and the "regular" method of adding a parameter, if (.string.IsNullOrEmpty(idFilter)) cmd.Parameters,Add(new NpgsqlParameter("idList"; idFilter); , also ucommented in the GetDgvData() function, the error at da.Fill(ds); is an NpgsqlPostgresException, "operator does not exist: integer = text'".当我只使用查询构建代码中未注释的行时, AND id IN (@idList)和添加参数的“常规”方法, if (.string.IsNullOrEmpty(idFilter)) cmd.Parameters,Add(new NpgsqlParameter("idList"; idFilter); ,也在GetDgvData() function 中注释, da.Fill(ds);处的错误是 NpgsqlPostgresException,“操作符不存在:integer = text'”

After some research I tried the UNNEST() function in the query code and left the parameter code.经过一番研究,我在查询代码中尝试了UNNEST() function 并留下了参数代码。 When I try that, I get an Exception saying that function doesn't exist.当我尝试这样做时,我得到一个异常说 function 不存在。

After some more research, I found a code example that casts the Array db type to an Integer db Type, which is the first line of commented code in the parameters section in the GetDgvData() function.经过一番研究,我发现了一个将 Array db 类型转换为 Integer db 类型的代码示例,这是GetDgvData() function 参数部分中注释代码的第一行。 When I tried that, the Exception thrown was, "System.InvalidCastException: 'Can't write type System.String as an array of System.Int32'".当我尝试这样做时,抛出的异常是“System.InvalidCastException:'无法将类型 System.String 写入 System.Int32 的数组'”。

After yet more research, I tried the third commented line in the GetDgvData() function, that surrounds the parameter with "Array[]" .经过更多研究,我尝试了GetDgvData() function 中的第三条注释行,它用"Array[]"包围了参数。 The Exception I get for that is, "operator does not exist: integer = text'".我得到的例外是,“操作员不存在:integer = text'”。 I tried that with and without the UNNEST() function in the query.我尝试在查询中使用和不使用UNNEST() function 。 Without UNNEST() I get a Npgsql.PostgresException, "operator does not exist: integer = text'".没有UNNEST()我得到一个 Npgsql.PostgresException,“操作符不存在:integer = text'”。 Using UNNEST() I get the PostgresException, "function unnest(text) does not exist'".使用UNNEST()我得到 PostgresException,“函数 unnest(text) 不存在”。

I also built the ids as a List<> and tried passing it to GetDgvData() but I'm not passing it correctly or something because I'm getting a null reference exception in GetDgvData() when it gets to the line where I try to do something with the List<> - including just trying to look at the contents in the Console.我还将 ID 构建为List<>并尝试将其传递给GetDgvData()但我没有正确传递它或其他什么,因为当它到达我尝试的行时,我在GetDgvData()中得到了 null 引用异常对List<>做一些事情 - 包括尝试查看控制台中的内容。

So I really don't know what else to try from here.所以我真的不知道还能从这里尝试什么。

Others have successfully used the above code in their respective solutions, so I can only assume I'm missing something, but what it could be, I have NO idea.其他人已经在他们各自的解决方案中成功地使用了上述代码,所以我只能假设我遗漏了一些东西,但它可能是什么,我不知道。

There is a lot to deconstruct, so I'm going to attempt to boil this down to just what I think is wrong.有很多东西要解构,所以我将尝试将其归结为我认为是错误的。 I am hopeful it is enough to get you on track.我希望这足以让你走上正轨。

It looks like you attempted to use an array, which is absolutely the way to go.看起来您尝试使用数组,这绝对是 go 的方式。 The problem is you used IN which is for discrete lists.问题是您使用了用于离散列表的IN For an array you need to use ANY .对于数组,您需要使用ANY

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

translates to翻译成

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

Secondly, unless you use dynamic SQL, if you call a bind variable, you need to assign a value to it.其次,除非你使用动态SQL,否则如果调用绑定变量,则需要为其赋值。 The converse is not true.反之则不成立。 So, once you declare @category in your SQL, you MUST assign it a value in C#.因此,一旦您在 SQL 中声明了@category ,您必须在 C# 中为其分配一个值。 One possible way around this is to wrap your C# logic within the SQL -- I don't believe you will experience any performance hit for this.解决此问题的一种可能方法是将 C# 逻辑包装在 SQL 中——我不相信您会因此遇到任何性能损失。

Your revised query would look something like 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

And from there, the key change I would make is to pass your Id List as an array of integers or something that can be converted to an array of integers via.ToArray().从那里开始,我要做的关键更改是将您的 Id List 作为整数数组或可以通过.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);

The rest of the code is the same.代码的rest是一样的。

Forgive my use of AddWithValue which I know is frowned upon in many circles... Candidly, I love it and feel the arguments against it don't outweight its usefulness, especially with PostgreSQL.请原谅我对AddWithValue的使用,我知道它在许多圈子中都被不赞成...坦率地说,我喜欢它,并且觉得反对它的 arguments 并没有超过它的实用性,尤其是 PostgreSQL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM