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";
}
我与您分享了一段代码,除了我试图在列表框的项目中循环的部分之外,它可以正常工作。这就是为什么我在这里向你寻求帮助。 最近,我从 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";
}