如何在GridView的RowUpdating事件中访问编辑行文本框并解决索引越界问题
问题根因说明
你遇到的索引超出范围报错,以及更新逻辑的异常,核心来自三个层面的问题:
- 索引取值逻辑完全依赖单元格
Controls[0]的固定位置,没有考虑GridView自动渲染时的控件层级 - 自动生成的编辑用TextBox没有分配固定ID,无法通过FindControl方法直接定位对象
- 原有更新SQL存在字段重复、主键取值错误、SQL注入风险的逻辑bug
具体问题拆解
1. 为什么Controls[0]会报索引超出范围
当GridView开启自动生成列(默认AutoGenerateColumns="true")时,每个单元格的Controls集合并不是直接把编辑用的TextBox放在索引0的位置:
- 渲染过程中,单元格会自动插入LiteralControl(包括页面空白、换行占位、命令按钮渲染的文本片段),这些占位控件会占据Controls集合的靠前位置
- 非编辑状态的单元格内仅包含纯文本控件,编辑状态下才会动态插入TextBox,插入位置没有固定索引规律
- 直接硬取
Controls[0]时,既可能遇到集合长度不足导致的索引越界,也可能遇到取到LiteralControl无法强转为TextBox的类型转换错误 - 这种无固定ID、无固定位置的控件结构,也导致你之前注释掉的
FindControl方法完全失效
2. 原有代码的逻辑缺陷
- 主键取值错误:更新时统一用Session存储的id做过滤条件,完全没有绑定当前编辑行的真实主键,会出现不管修改哪一行,都更新同一条固定记录的问题
- 更新SQL逻辑错误:语句中重复写了两次
set description,漏掉了drcr、categoryName字段的更新逻辑 - 安全风险:直接拼接用户输入的字符串生成SQL,存在SQL注入隐患
修正后的实现方案
方案一:保留自动生成列,安全获取编辑文本框
不要硬取固定控件索引,写通用方法遍历单元格内的控件找到TextBox对象:
// 递归查找单元格内的TextBox控件 private TextBox FindTextBoxInCell(TableCell cell) { foreach (Control ctrl in cell.Controls) { if (ctrl is TextBox tb) return tb; if (ctrl.HasControls()) { var nestedTb = FindTextBoxInCell((TableCell)ctrl); if (nestedTb != null) return nestedTb; } } return null; }
在RowUpdating事件中按列的顺序(和数据绑定返回的列顺序对齐)取值:
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e) { GridViewRow row = (GridViewRow)GridView1.Rows[e.RowIndex]; // 按列顺序找TextBox,和GVBind返回的列顺序一一对应 TextBox textCat = FindTextBoxInCell(row.Cells[0]); TextBox textDes = FindTextBoxInCell(row.Cells[1]); TextBox textPrice = FindTextBoxInCell(row.Cells[2]); TextBox textdrcr = FindTextBoxInCell(row.Cells[3]); // 获取当前编辑行的真实主键,不要用Session里的固定id int updateRowId = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value); GridView1.EditIndex = -1; // 用参数化SQL避免注入和字段错误 string connStr = ConfigurationManager.ConnectionStrings["cs"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) { string updateSql = @"update tblExp2 set categoryName=@Category, description=@Description, price=@Price, drcr=@Drcr where id=@Id"; SqlCommand cmd = new SqlCommand(updateSql, conn); // 给参数赋值,搭配类型校验保证输入合法 cmd.Parameters.AddWithValue("@Category", textCat.Text.Trim()); cmd.Parameters.AddWithValue("@Description", textDes.Text.Trim()); cmd.Parameters.AddWithValue("@Price", decimal.Parse(textPrice.Text.Trim())); cmd.Parameters.AddWithValue("@Drcr", textdrcr.Text.Trim()); cmd.Parameters.AddWithValue("@Id", updateRowId); conn.Open(); cmd.ExecuteNonQuery(); } // 重新绑定数据刷新GridView GVBind(); }
注意:如果用这个方案,需要在GridView标签上配置主键字段,才能用
DataKeys取值:<asp:GridView ID="GridView1" runat="server" DataKeyNames="id" ...>
方案二:手动定义列(更推荐,长期维护性更高)
放弃全自动生成列,手动绑定列,结构更可控,不会出现控件位置错乱的问题:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="id" OnRowUpdating="GridView1_RowUpdating"> <Columns> <asp:BoundField DataField="categoryName" HeaderText="Category" /> <asp:BoundField DataField="description" HeaderText="Description" /> <asp:BoundField DataField="price" HeaderText="Price" /> <asp:BoundField DataField="drcr" HeaderText="Drcr" /> <asp:CommandField ShowEditButton="True" ShowCancelButton="True" /> </Columns> </asp:GridView>
这种结构下,编辑模式的TextBox位置是固定的,配合上面的找控件方法不会出现索引越界问题。
注意事项
- 不要在页面回发时随意重新绑定GridView,否则会导致编辑状态丢失、事件触发异常
- 用户输入的数值字段(比如price)建议加输入合法性校验,避免类型转换报错
- GVBind函数里的查询SQL也建议改成参数化写法,避免同样的注入风险
内容的提问来源于stack exchange,提问作者Ishjot Singh
相关产品推荐
相关产品推荐

