DataGridView 添加、编辑和删除(带分页)






4.76/5 (40投票s)
本文介绍了在 DataGridView 中进行添加、编辑和删除操作,并结合分页和异步方法调用。
引言
本文(或者说代码片段)演示了一个简单的应用程序,使用 DataGridView
进行插入、更新和删除操作。该应用程序对大部分数据库调用使用了异步架构,目的是在不阻塞 UI 的情况下,允许用户继续执行其他任务。该应用程序的结构如下:
- 从网络中获取所有 SQL Server 实例。
- 从选定的实例中获取所有数据库。
- 从选定的数据库中获取所有表。
- 从选定的表中获取所有记录。
- 在
DataGridView
中添加、编辑、删除记录。 - 同时提供了分页功能,并可设置每页记录数。
如果用户提供的用户名或密码为空,或者用户名或密码不正确,则会返回相同的 SQL Server 实例列表。代码可在此处找到[^]。
异步架构
该应用程序使用异步调用机制进行数据库调用。由于 SQLEnumerator
不支持异步调用,我添加了委托(delegates)来异步调用这些方法。在 .NET 2.0 中,SqlCommand
对象支持对数据库的异步调用。我正在使用此功能从选定的数据库中获取所有表。委托是设计异步架构的最佳实践,因为 CLR 会在内部处理大部分事务,我们不必过多担心。
应用程序结构
当我们开始从头阅读代码时,可以看到一个名为 CallFor
的 enum
。当调用 SQL Server 列表、数据库和表时,它用于设置一个 private
变量 called
。这样做是因为只有一个回调方法处理来自异步调用的所有回调。一个 switch case
语句管理不同回调的行为。我使用 SqlCommandBuilder
设计了这个应用程序,因为我对如何使用 SqlCommandBuilder
有一些疑问。只要在你的 Select
查询中选择了主键,构建器就会自动生成插入、更新和删除命令。我遇到了一个非常常见的跨线程异常问题。但在我之前的项目中,我们实现了相同的架构,.NET 2.0 提供了 InvokeRequired
和 Invoke()
函数来克服这个问题。我们需要声明一个与回调方法具有相似签名的委托,并通过控件的 Invoke()
方法调用相同的方法。这将把调用堆栈从执行线程推到父线程,让父线程去处理。你需要遵循这样的顺序:
- 获取所有 SQL Server 实例。
- 从选定的实例中获取所有数据库。
- 从选定的数据库中获取所有表。
- 加载选定表的数据。
- 使用分页进行导航。
- 添加“添加/更新”、“提交”、“删除”等按钮来插入/更新或删除。您可以删除/更新/插入多条记录。
这里有一些代码块我将进行解释。这个函数设置了应用程序所需的数据库对象。sqlQuery
是动态构建的。
private void SetDataObjects()
{
connection = new SqlConnection(connectionString);
command = new SqlCommand(sqlQuery, connection);
adapter = new SqlDataAdapter(command);
builder = new SqlCommandBuilder(adapter);
ds = new DataSet("MainDataSet");
tempDataSet = new DataSet("TempDataSet");
}
下面的两个函数加载数据并将其绑定到 DataGridView
。我曾尝试将一个临时的 DataTable
对象绑定到 DataGridView
,然后再更新主表。但我未能做到。我使用了适配器的 Fill()
方法,该方法将起始记录和记录数作为输入参数与 DataSet
一起使用。我创建了一个临时的 DataSet
来获取总记录数。然后立即将其释放。我更喜欢手动添加列,然后让行绑定到这些列,而不是直接将数据源绑定到 DataGridView
。这允许排序,并且在不希望显示某些列的情况下,可以在这里进行处理。
private void btnLoad_Click(object sender, EventArgs e)
{
lblLoadedTable.Text = "Loading data from table " + cmbTables.Text.Trim();
btnLoad.Enabled = false;
this.Cursor = Cursors.WaitCursor;
try
{
if (userTable != null)
{
userTable.Clear();
}
userDataGridView.DataSource = null;
userDataGridView.Rows.Clear();
userDataGridView.Refresh();
sqlQuery = "SELECT * FROM [" + cmbTables.Text.Trim() + "]";
SetDataObjects();
connection.Open();
ticker.Start();
adapter.Fill(tempDataSet);
totalRecords = tempDataSet.Tables[0].Rows.Count;
tempDataSet.Clear();
tempDataSet.Dispose();
adapter.Fill(ds, 0, 5, cmbTables.Text.Trim());
userTable = ds.Tables[cmbTables.Text.Trim()];
foreach (DataColumn dc in userTable.Columns)
{
DataGridViewTextBoxColumn column = new DataGridViewTextBoxColumn();
column.DataPropertyName = dc.ColumnName;
column.HeaderText = dc.ColumnName;
column.Name = dc.ColumnName;
column.SortMode = DataGridViewColumnSortMode.Automatic;
column.ValueType = dc.DataType;
userDataGridView.Columns.Add(column);
}
lblLoadedTable.Text = "Data loaded from table: " + userTable.TableName;
lblTotRecords.Text = "Total records: " + totalRecords;
CreateTempTable(0, int.Parse(cmbNoOfRecords.Text.Trim()));
btnPrevious.Enabled = true;
btnFirst.Enabled = true;
btnPrevious.Enabled = true;
btnNext.Enabled = true;
btnLast.Enabled = true;
btnAdd.Enabled = true;
btnUpdate.Enabled = true;
btnDelete.Enabled = true;
cmbNoOfRecords.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
connection.Close();
btnLoad.Enabled = true;
this.Cursor = Cursors.Default;
prgProgress.Value = 0;
prgProgress.Update();
prgProgress.Refresh();
ticker.Stop();
}
}
此方法实际上是将数据绑定到 DataGridView
,并为所有分页函数调用。根据当前索引等信息,获取所需的记录数。
private void CreateTempTable(int startRecord, int noOfRecords)
{
if (startRecord == 0 || startRecord < 0)
{
btnPrevious.Enabled = false;
startRecord = 0;
}
int endRecord = startRecord + noOfRecords;
if (endRecord >= totalRecords)
{
btnNext.Enabled = false;
isLastPage = true;
endRecord = totalRecords;
}
currentPageStartRecord = startRecord;
currentPageEndRecord = endRecord;
lblPageNums.Text = "Records from " + startRecord + " to "
+ endRecord+ " of " + totalRecords;
currentIndex = endRecord;
try
{
userTable.Rows.Clear();
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
adapter.Fill(ds, startRecord, noOfRecords, cmbTables.Text.Trim());
userTable = ds.Tables[cmbTables.Text.Trim()];
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
connection.Close();
}
userDataGridView.DataSource = userTable.DefaultView;
userDataGridView.AllowUserToResizeColumns = true;
}
这是一个获取 SQL Server 实例的异步方法的示例。在 BeginInvoke()
之后,用户可以自由执行任何任务。回调函数将捕获响应。这就是您可以使用委托异步调用方法的方式。
private void btnLoadSqlServers_Click(object sender, EventArgs e)
{
ticker.Start();
btnLoadSqlServers.Enabled = false;
this.Cursor = Cursors.WaitCursor;
cmbSqlServers.Items.Clear();
called = CallFor.SqlServerList;
intlDelg.BeginInvoke(new AsyncCallback(CallBackMethod), intlDelg);
}
这是将处理来自不同方法的各种回调的回调方法。当您使用异步架构时,回调总是在一个与父线程不同的新线程上进行。这个新线程无法访问父线程上的控件。因此,我们需要将调用堆栈转移到父线程,这可以通过 control.InvokeRequired
和 control.Invoke()
来实现。以下方法展示了如何做到这一点。
private void CallBackMethod(IAsyncResult result)
{
if (this.InvokeRequired)
{
this.Invoke(new AsyncDelegate(CallBackMethod), result);
}
else
{
try
{
prgProgress.Value = prgProgress.Maximum;
switch (called)
{
case CallFor.SqlServerList:
string[] sqlServers = intlDelg.EndInvoke(result);
cmbSqlServers.Items.AddRange(sqlServers);
if (cmbSqlServers.Items.Count > 0)
{
cmbSqlServers.Sorted = true;
cmbSqlServers.SelectedIndex = 0;
}
this.Cursor = Cursors.Default;
btnLoadSqlServers.Enabled = true;
txtUserName.Select();
txtUserName.Focus();
break;
case CallFor.SqlDataBases:
string[] sqlDatabases = intlDelg.EndInvoke(result);
cmbAllDataBases.Items.AddRange(sqlDatabases);
if (cmbAllDataBases.Items.Count > 0)
{
cmbAllDataBases.Sorted = true;
cmbAllDataBases.SelectedIndex = 0;
}
this.Cursor = Cursors.Default;
btnGetAllDataBases.Enabled = true;
break;
case CallFor.SqlTables:
reader = command.EndExecuteReader(result);
cmbTables.Items.Clear();
while (reader.Read())
{
cmbTables.Items.Add(reader[0].ToString());
}
if (cmbTables.Items.Count > 0)
{
cmbTables.Sorted = true;
cmbTables.SelectedIndex = 0;
grpDataManipulate.Enabled = true;
}
else
{
grpDataManipulate.Enabled = false;
}
break;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
if (called == CallFor.SqlTables)
{
btnGetAllTables.Enabled = true;
this.Cursor = Cursors.Default;
}
prgProgress.Value = 0;
prgProgress.Refresh();
ticker.Stop();
}
}
}
结论
这样,我们就可以轻松地操作 DataGridView
。唯一需要注意的是,我们必须使用直接绑定到 DataGridView
的数据源。在加载大量数据时,异步调用会非常高效。如果您有任何疑问,请告诉我。