C#操作SQL单条企业信息:插入更新报错及方案咨询
问题描述
在Visual Studio 2019中设计企业信息表单,需将唯一的企业信息保存到SQL表的单条记录中。采用先查询表中id为1的记录,根据是否存在及CompanyName是否为空来决定执行插入或更新操作,但运行时出现错误:System.IndexOutOfRangeException: 'CompanyName'。相关C#代码如下:
private void btnSave_Click(object sender, EventArgs e) { cmd = new SqlCommand("select * from CompanyProfile where id=@N", con); cmd.Parameters.AddWithValue("@N", "1"); if (cmd.Connection.State == ConnectionState.Open) { cmd.Connection.Close(); } con.Open(); dr = cmd.ExecuteReader(); if (dr.Read()) { if (dr["CompanyName"].ToString() == null) { con.Close(); SqlCommand cmd = new SqlCommand("insert into CompanyProfile(ComanyName,RegNo,RegDate,NID,EID,WorkShopCode,WebSite,CEOName,Tel,Mobile,Fax,ZipCode,Email,Address,Logo)values(@a,@b,@c,@d,@e,@f,@g,@h,@i,@j,@k,@l,@m,@n,@o)", con); if (PBLogo.Image == null) { MessageBox.Show("please insert PIC"); return; } if (cmd.Connection.State == ConnectionState.Open) { cmd.Connection.Close(); } con.Open(); byte[] ar = File.ReadAllBytes(PBLogo.ImageLocation); cmd.Parameters.AddWithValue("@a", txtCompanyName.Text); cmd.Parameters.AddWithValue("@b", txtRegNo.Text); cmd.Parameters.AddWithValue("@c", txtRegDate.Text); cmd.Parameters.AddWithValue("@d", txtNID.Text); cmd.Parameters.AddWithValue("@e", txtEID.Text); cmd.Parameters.AddWithValue("@f", txtWorkShopCode.Text); cmd.Parameters.AddWithValue("@g", txtWebSite.Text); cmd.Parameters.AddWithValue("@h", txtCEOName.Text); cmd.Parameters.AddWithValue("@i", txtTel.Text); cmd.Parameters.AddWithValue("@j", txtMobile.Text); cmd.Parameters.AddWithValue("@k", txtFax.Text); cmd.Parameters.AddWithValue("@l", txtZipCode.Text); cmd.Parameters.AddWithValue("@m", txtEmail.Text); cmd.Parameters.AddWithValue("@n", txtAddress.Text); cmd.Parameters.AddWithValue("@o", SqlDbType.VarBinary).Value = ar; cmd.ExecuteNonQuery(); con.Close(); MessageBox.Show("form saved"); } else { con.Close(); MemoryStream ms1 = new MemoryStream(); try { byte[] arrPic = ms1.GetBuffer(); ms1.Close(); cmd.Parameters.Clear(); cmd.Connection = con; string Updatetable = "Update CompanyProfile set CompanyName='" + txtCompanyName.Text + "',RegNo='" + txtRegNo.Text + "',RegDate='" + txtCompanyName.Text + "',NID='" + txtNID.Text + "',EID='" + txtEID.Text + "',WorkShopCode='" + txtWorkShopCode.Text + "',Website='" + txtWebSite.Text + "',CEOName='" + txtCEOName.Text + "',Tel='" + txtTel.Text + "',Mobile='" + txtMobile.Text + "',Fax='" + txtFax.Text + "',ZipCode='" + txtZipCode.Text + "',Email='" + txtEmail.Text + "',Address='" + txtAddress.Text + "',Pic=@Pic where id=@Y"; cmd.Parameters.AddWithValue("@Y", "1"); SqlCommand com = new SqlCommand(Updatetable, con); com.Parameters.AddWithValue("@Pic", arrPic); con.Open(); com.ExecuteNonQuery(); con.Close(); MessageBox.Show("form updated"); } catch (Exception ex) { MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }
咨询两个问题:
- 该错误的原因是什么,如何修复?
- 当前这种实现单条记录插入或更新的方式是否合适?
解答
1. 错误原因及修复方案
错误原因
System.IndexOutOfRangeException: 'CompanyName' 本质是DataReader中找不到指定的CompanyName列,具体诱因包括:
- 字段拼写错误:插入语句中写的是
ComanyName(少了一个字母p),如果数据库表实际字段是CompanyName,会导致插入时未给目标字段赋值,后续查询时DataReader虽包含CompanyName列,但逻辑判断错误;若表中被误创建ComanyName字段,代码里用dr["CompanyName"]自然找不到对应列。 - NULL判断逻辑错误:直接用
dr["CompanyName"].ToString() == null判断NULL值不成立——即使字段为NULL,ToString()会返回空字符串而非null,且若字段不存在,直接访问dr["CompanyName"]会触发索引越界。 - 字段名大小写/不一致:更新语句中写的是
Website,但插入语句用的是WebSite,若数据库采用区分大小写的排序规则,会导致列名不匹配。
修复步骤
- 修正字段拼写:将插入语句中的
ComanyName改为CompanyName,更新语句中的Website改为WebSite,确保与数据库表字段名完全一致; - 修正NULL判断逻辑:替换原判断为
if (dr.IsDBNull(dr.GetOrdinal("CompanyName")) || string.IsNullOrWhiteSpace(dr["CompanyName"].ToString())),先判断字段是否为NULL,再判断内容是否为空; - 明确查询列:将查询语句从
select *改为select CompanyName from CompanyProfile where id=@N,只返回需要的列,避免不必要的列干扰; - 避免全局资源泄漏:不要使用全局的
SqlCommand、SqlDataReader和SqlConnection,在方法内声明并使用using语句自动释放资源。
2. 当前实现方式的合理性及优化方案
当前方式的问题
- 并发风险:先查询后操作的逻辑不是原子性的,多用户同时操作时可能出现重复插入;
- SQL注入漏洞:更新语句直接拼接字符串(如
CompanyName='" + txtCompanyName.Text + "'),存在严重的注入风险; - 资源泄漏:全局连接、命令未正确释放,易导致数据库连接池耗尽;
- 图片处理错误:更新时的
MemoryStream未读取实际图片内容,导致更新的图片为空; - 逻辑冗余:手动处理查询、插入、更新分支,代码繁琐易出错。
更优实现方案
使用SQL Server的MERGE语句,在数据库层面完成原子性的插入/更新操作,简化代码同时避免并发问题:
优化后代码示例
private void btnSave_Click(object sender, EventArgs e) { if (PBLogo.Image == null) { MessageBox.Show("please insert PIC"); return; } // 正确转换图片为字节数组 byte[] logoBytes = null; using (var ms = new MemoryStream()) { PBLogo.Image.Save(ms, PBLogo.Image.RawFormat); logoBytes = ms.ToArray(); } // 原子性插入/更新的MERGE语句 string mergeSql = @" MERGE INTO CompanyProfile AS Target USING (SELECT @Id AS Id) AS Source ON Target.Id = Source.Id WHEN MATCHED THEN UPDATE SET CompanyName = @CompanyName, RegNo = @RegNo, RegDate = @RegDate, NID = @NID, EID = @EID, WorkShopCode = @WorkShopCode, WebSite = @WebSite, CEOName = @CEOName, Tel = @Tel, Mobile = @Mobile, Fax = @Fax, ZipCode = @ZipCode, Email = @Email, Address = @Address, Logo = @Logo WHEN NOT MATCHED THEN INSERT (Id, CompanyName, RegNo, RegDate, NID, EID, WorkShopCode, WebSite, CEOName, Tel, Mobile, Fax, ZipCode, Email, Address, Logo) VALUES (@Id, @CompanyName, @RegNo, @RegDate, @NID, @EID, @WorkShopCode, @WebSite, @CEOName, @Tel, @Mobile, @Fax, @ZipCode, @Email, @Address, @Logo); "; // 使用using自动释放资源 using (var con = new SqlConnection("你的数据库连接字符串")) using (var cmd = new SqlCommand(mergeSql, con)) { cmd.Parameters.AddWithValue("@Id", 1); cmd.Parameters.AddWithValue("@CompanyName", txtCompanyName.Text); cmd.Parameters.AddWithValue("@RegNo", txtRegNo.Text); cmd.Parameters.AddWithValue("@RegDate", txtRegDate.Text); cmd.Parameters.AddWithValue("@NID", txtNID.Text); cmd.Parameters.AddWithValue("@EID", txtEID.Text); cmd.Parameters.AddWithValue("@WorkShopCode", txtWorkShopCode.Text); cmd.Parameters.AddWithValue("@WebSite", txtWebSite.Text); cmd.Parameters.AddWithValue("@CEOName", txtCEOName.Text); cmd.Parameters.AddWithValue("@Tel", txtTel.Text); cmd.Parameters.AddWithValue("@Mobile", txtMobile.Text); cmd.Parameters.AddWithValue("@Fax", txtFax.Text); cmd.Parameters.AddWithValue("@ZipCode", txtZipCode.Text); cmd.Parameters.AddWithValue("@Email", txtEmail.Text); cmd.Parameters.AddWithValue("@Address", txtAddress.Text); cmd.Parameters.Add("@Logo", SqlDbType.VarBinary).Value = logoBytes; try { con.Open(); cmd.ExecuteNonQuery(); MessageBox.Show("操作成功"); } catch (Exception ex) { MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
优化点说明
- 原子操作:
MERGE语句在数据库层面完成判断和操作,避免并发冲突; - 防SQL注入:全参数化查询,无字符串拼接;
- 资源自动释放:
using语句自动回收数据库连接和命令,避免泄漏; - 逻辑简化:无需手动处理查询分支,代码更简洁;
- 正确处理图片:将控件中的图片正确转换为字节数组,避免空值问题。
内容的提问来源于stack exchange,提问作者farhadsph
相关产品推荐
相关产品推荐

