C# C0246 使用项目来自 SQL 服务器的列表框过滤 DataGridView 时

C# C0246 While Filtering DataGridView with Listbox whose items come from SQL Server

我与您分享了一段代码,除了我试图在列表框的项目中循环的部分之外,它可以正常工作。这就是为什么我在这里向你寻求帮助。 最近,我从 VBA 切换到 C#,所以我对此还是新手,还没有理解所有内容。 因此,下面的代码连接到我的 SQL 服务器数据库,并在我的列表框和 DataGridView 中获取数据。我也可以使用两个文本框进行过滤。 所以现在我的列表框中有项目,DataGridview 中有我的数据库视图。我想用列表框的项目过滤我的 DataGridview(由数据表填充)。我想我只想念一个愚蠢的部分。为什么我得到这个 CS0246“找不到 ListItem”

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsAppTest
{

public partial class Form1 : Form

{
   //Initialize the component and display the items within my listbox CS_Bonds_listBox
    public Form1()
    {
        InitializeComponent();
        string connetionString = @"Data Source=my_server;Initial Catalog=my_db;Integrated Security=SSPI";
        SqlConnection conn = new SqlConnection(connetionString);
        conn.Open();
        DataSet ds = new DataSet();
        SqlDataAdapter adapter = new SqlDataAdapter(
        "SELECT DISTINCT RatingProvider FROM Bonds", conn);
        adapter.Fill(ds);
        this.CS_Bonds_listBox.DataSource = ds.Tables[0];
        this.CS_Bonds_listBox.DisplayMember = "RatingProvider";
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    DataTable dtTEST = new DataTable();

// Next, when clicking on my button Connect, I retrieve my db into a Datatable that is displayed within //the Datagridview1

    private void buttonConnect_Click(object sender, EventArgs e)
    {
        string connetionString = @"Data Source=my_server;Initial Catalog=my_db;Integrated Security=SSPI";
        SqlConnection cnn= new SqlConnection(connetionString);
        cnn.Open();
        MessageBox.Show("Connection Open  !");
        String sql = "Select * from Bonds";
        SqlCommand command = new SqlCommand(sql, cnn);
        SqlDataAdapter sqlDA = new SqlDataAdapter();
        sqlDA.SelectCommand = command;
        sqlDA.Fill(dtTEST);
        dataGridView1.DataSource = dtTEST;

        cnn.Close();
    }

    private void ISIN_Bonds_textBox_TextChanged(object sender, EventArgs e)
    {
        DataView dv = dtTEST.DefaultView;
        dv.RowFilter = "ISIN LIKE '" + ISIN_Bonds_textBox.Text + "%'";
        dataGridView1.DataSource = dv;
    }

    private void Ticker_Bonds_textBox_TextChanged(object sender, EventArgs e)
    {
        DataView dv1 = dtTEST.DefaultView;
        dv1.RowFilter = "Ticker LIKE '" + Ticker_Bonds_textBox.Text + "%'";
        dataGridView1.DataSource = dv1;
    }


    private void CS_Bonds_listBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        string conString = @"Data Source=my_server;Initial Catalog=my_db;Integrated Security=SSPI";           
        string query = "SELECT ISIN, Ticker, CrediSight, FROM Bonds";
        string condition = string.Empty;

        foreach (ListItem item in CS_Bonds_listBox.Items)
        {
            condition += item.Selected ? string.Format("'{0}',", item.Value) : "";
        }

        if (!string.IsNullOrEmpty(condition))
        {
            condition = string.Format(" WHERE Country IN ({0})", condition.Substring(0, condition.Length - 1));
        }

        using (SqlConnection con = new SqlConnection(conString))
        {
            using (SqlCommand cmd = new SqlCommand(query + condition))
            {
                using (SqlDataAdapter sda = new SqlDataAdapter(cmd))
                {
                    cmd.Connection = con;
                    using (DataTable dt = new DataTable())
                    {
                        sda.Fill(dt);
                        dataGridView1.DataSource = dt;
                        //dataGridView1.DataBind();
                    }
                }
            }
        }
    }
 
  }
}

这行有问题:

foreach (ListItem item in CS_Bonds_listBox.Items)

ListItem 是 WebForms 的东西,而您的应用程序是 WinForms 的东西;您的列表框不包含 ListItem 对象列表,因此即使导入了相关的网络名称空间,这行代码也无法正常工作。

因为您已将列表框绑定到数据表,所以它显示的列表充满了 DataRowView 对象,因此这就是您需要处理的内容。 DataRowView 有一个 Row 属性 为您提供基础行,而该行又可以通过列名访问。

此外,为了让您的生活更轻松,列表框有一个 SelectedItems 属性,因此您不需要检查每个项目是否被选中:

foreach (DataRowView drv in CS_Bonds_listBox.SelectedItems)
{
  var dr = drv.Row as DataRow;
  var rp = dr["RatingProvider"]; 
  condition += $"'{rp}'," 
}

因此,您的条件将以尾随逗号结尾,因此 trim 在您使用它构建 IN 子句之前将其关闭:

condition = condition.TrimEnd(',');

如果用户设法更改列表项中显示的文本,此技术可能容易受到 SQL 注入攻击。

处理该问题的更好方法是通过参数化。你会这样做:

var cmd  = new SqlCommand("SELECT * FROM table WHERE Country IN(", connStr);

int i = 0;
foreach (DataRowView drv in CS_Bonds_listBox.SelectedItems)
{
  var dr = drv.Row as DataRow;
  var rp = dr["RatingProvider"]; 
  cmd.CommandText += $"@p{i},";
  cmd.Parameters.Add($"@p{i}", SqlDbType.VarChar).Value = rp;
  i++;
}

cmd.CommandText = cmd.CommandText.TrimEnd(',') + ")";
using(var da = new SqlDataAdapter(cmd))
{
  var dt = new DataTable();
  da.Fill(dt);
  someGridView.DataSource = dt;
}

这构建了一个看起来像 SELECT * FROM table WHERE Country IN(@p0,@p1,@p2.... 的 sql 即我们将参数占位符串联起来而不是将值串联起来。同时我们用参数值

这也意味着我们的数据库不能是hacked via our program,当用户选择一个名字像Cote d'Ivoire[=19=的国家时,我们的应用程序不会死在堆里]


整理代码时需要注意的其他事项:

SqlDataAdapter 可以接受一个字符串 SQL 和一个字符串 connection-string。您不需要为它创建 SqlCommand。您不需要为它打开和关闭连接;它知道如何自己做这一切。我只使用了 SqlCommand,因为我正在构建参数集合。通常我会 using(var da = SqlDataAdapter("SELECT...", "Server=..") 因为它让事情变得漂亮和整洁。

这意味着例如你的构造函数可以很简单:

//put this here once
private string _connStr = @"Data Source=my_server;Initial Catalog=my_db;Integrated Security=SSPI";

public Form1()
{
    InitializeComponent();
    
    var dt = new DataTable();
    using(var da = new SqlDataAdapter("SELECT DISTINCT RatingProvider FROM Bonds", _connStr))
      adapter.Fill(dt);
    this.CS_Bonds_listBox.DataSource = dt;
    this.CS_Bonds_listBox.DisplayMember = "RatingProvider";
}