GridView PageIndexChanging事件中保存选中行至SQL Server的问题
这个问题我之前也碰到过,核心原因是在PageIndexChanging事件里,你先执行了分页绑定,再去遍历GridView.Rows,这时候拿到的已经是新页的行数据,而不是用户刚才选中的旧页的行。按钮回发时能成功,是因为按钮点击时GridView还没切换页面,Rows还是当前用户操作的那一页的内容。
下面给你一步步的解决方案:
一、先搞懂为什么PageIndexChanging里存不了
当用户点击分页按钮时,页面回发的事件顺序是:
- 页面加载并恢复ViewState
- 触发
PageIndexChanging事件 - 你设置
GridView.PageIndex = e.NewPageIndex然后DataBind,此时GridView会重新绑定新页的数据,原来旧页的行和选中状态都被覆盖 - 你再遍历
GridView.Rows,拿到的是新页的未选中行,自然存不了数据
所以正确的顺序应该是:先保存当前页的选中行 → 再执行分页绑定 → 最后把保存的数据存入数据库
二、具体实现步骤
1. 封装GridView绑定方法
把重复的绑定逻辑抽出来,方便复用和维护,同时还能恢复选中状态(可选,提升用户体验):
private void BindGridView() { // 用参数化查询避免SQL注入! string query2 = @"select QuestionID, QuestionText, q.QuizID, TopicID from StandardQuizData qd join Questions q on qd.QuizID = q.QuizID where qd.TopicID = @TopicID"; using (var adapt2 = new SqlDataAdapter(query2, connString)) { // 添加参数,防止SQL注入 adapt2.SelectCommand.Parameters.AddWithValue("@TopicID", Request.QueryString["UserCustomQuizTopicID"]); var ds2 = new DataSet(); adapt2.Fill(ds2, "Questions"); GridView1.DataSource = ds2; GridView1.DataBind(); } // 恢复之前保存的选中状态(如果需要) RestoreSelectedRowState(); }
2. 保存当前页的选中行数据
在分页前,先把用户选中的行数据存到ViewState(或者Session,根据你的需求):
private void SaveSelectedRows() { DataTable selectedTable = new DataTable(); // 和你要存入数据库的列保持一致 selectedTable.Columns.Add("CustomQuizQID", typeof(string)); selectedTable.Columns.Add("UserID", typeof(string)); selectedTable.Columns.Add("QuizID", typeof(string)); selectedTable.Columns.Add("TopicID", typeof(string)); selectedTable.Columns.Add("QuestionID", typeof(string)); selectedTable.Columns.Add("QuestionName", typeof(string)); foreach (GridViewRow gvrow in GridView1.Rows) { var checkbox = gvrow.FindControl("CheckBoxSelect") as CheckBox; if (checkbox != null && checkbox.Checked) { DataRow drow = selectedTable.NewRow(); drow["UserID"] = User.Identity.GetUserId().ToString(); drow["QuizID"] = Request.QueryString["UserCustomQuizID"].ToString(); drow["TopicID"] = gvrow.Cells[0].Text; drow["QuestionID"] = gvrow.Cells[1].Text; drow["QuestionName"] = gvrow.Cells[2].Text; selectedTable.Rows.Add(drow); } } // 存到ViewState里,后续可以取用 ViewState["SelectedRows"] = selectedTable; }
3. 恢复选中状态(可选)
如果希望用户切换页面后,之前选中的行(如果在当前页)依然保持选中,可以加这个方法:
private void RestoreSelectedRowState() { if (ViewState["SelectedRows"] == null) return; DataTable selectedTable = (DataTable)ViewState["SelectedRows"]; foreach (GridViewRow gvrow in GridView1.Rows) { string currentQuestionID = gvrow.Cells[1].Text; // 检查当前行的QuestionID是否在已选中的列表里 var matchedRow = selectedTable.AsEnumerable() .FirstOrDefault(r => r["QuestionID"].ToString() == currentQuestionID); if (matchedRow != null) { var checkbox = gvrow.FindControl("CheckBoxSelect") as CheckBox; if (checkbox != null) checkbox.Checked = true; } } }
4. 修改PageIndexChanging事件
现在调整事件逻辑,先保存再分页再保存到数据库:
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e) { try { // 第一步:先保存当前页的选中行 SaveSelectedRows(); // 第二步:把选中的数据存入数据库 SaveSelectedRowsToDatabase(); // 第三步:执行分页并绑定数据 GridView1.PageIndex = e.NewPageIndex; BindGridView(); } catch (Exception ex) { Response.Write($"操作出错:{ex.Message}"); } } private void SaveSelectedRowsToDatabase() { if (ViewState["SelectedRows"] == null) return; DataTable tableUserInput = (DataTable)ViewState["SelectedRows"]; if (tableUserInput.Rows.Count == 0) return; using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connString)) { bulkCopy.DestinationTableName = "CustomQuizQuestions"; try { bulkCopy.WriteToServer(tableUserInput); // 保存成功后可以清空ViewState,避免重复保存 ViewState["SelectedRows"] = null; } catch (Exception ex) { Response.Write($"批量保存数据出错:{ex.Message}"); } } }
三、额外建议
- 避免分页时自动保存:用户可能只是切换页面查看,还没确认要保存所有选中项,建议加一个「保存选中项」的按钮,在按钮的Click事件里调用
SaveSelectedRows()和SaveSelectedRowsToDatabase(),这样更符合用户操作逻辑。 - 消除SQL注入风险:原来的SQL语句直接拼接
Request.QueryString的值,非常危险,一定要用参数化查询(上面的代码已经改了)。 - 错误日志代替Response.Write:正式环境里不要用
Response.Write输出错误,应该用日志框架(比如NLog、Serilog)记录错误信息,避免泄露敏感信息给用户。
内容的提问来源于stack exchange,提问作者Rayomand
相关产品推荐
相关产品推荐

