C# WinForms:如何将DataGridView的DataSet变更从UI层传至数据层
问题分析与解决方案
核心错误原因
- UI层实例不一致:窗体中
PartnerRepository属性每次被访问都会创建新的PartnerRepository实例。加载数据时用的是实例A,保存时用的是实例B,实例B的ds、adapter字段完全未初始化,调用SaveAddressChangesData时触发NullReferenceException。 - 数据层有状态设计缺陷:依赖类级别的
ds、adapter字段保存状态,导致必须用同一个实例才能完成“加载-修改-保存”流程,不仅增加耦合,还容易引发空引用问题。
代码修复方案
第一步:修正UI层的实例管理
将PartnerRepository改为私有字段,仅在窗体初始化时创建一次,确保加载和保存用同一个实例:
namespace App.UI { public partial class PartnerForm : Form { // 改为私有只读字段,仅初始化一次 private readonly App.Data.PartnerRepository _partnerRepository; private DataSet _addressDataSet; public PartnerForm () { InitializeComponent(); _partnerRepository = new App.Data.PartnerRepository(); // 传入对应的id参数(原方法需要int类型参数) _addressDataSet = _partnerRepository.AddressListDataSet(1); addressDataGridView.DataSource = _addressDataSet.Tables[0].DefaultView; } private void addressDataGridView_KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { var changes = _addressDataSet.GetChanges(); if (changes != null) { // 将修改后的DataSet传递给数据层处理 _partnerRepository.SaveAddressChangesData(_addressDataSet); } } } } }
第二步:重构数据层为无状态设计
移除类级别的状态字段,改用局部变量和参数传递,避免依赖实例状态:
namespace App.Data { public class PartnerRepository { public DataSet AddressListDataSet(int id) { using var conn = new MySqlConnection(DB.connString); // 替换为实际查询语句 var adapter = new MySqlDataAdapter($"SELECT * FROM your_table WHERE id = {id}", conn); var ds = new DataSet(); adapter.Fill(ds); // 将DataAdapter存入DataSet的扩展属性,供后续更新使用 ds.ExtendedProperties["DataAdapter"] = adapter; return ds; } public void SaveAddressChangesData(DataSet dataSet) { try { var changes = dataSet.GetChanges(); if (changes == null) return; // 从DataSet中取出之前保存的DataAdapter if (dataSet.ExtendedProperties.TryGetValue("DataAdapter", out var adapterObj) && adapterObj is MySqlDataAdapter adapter) { var builder = new MySqlCommandBuilder(adapter); adapter.Update(changes); dataSet.AcceptChanges(); } } catch (Exception ex) { dataSet.RejectChanges(); // 可添加异常日志记录,例如:Console.WriteLine($"保存失败:{ex.Message}"); } } } }
替代方案(无需保存DataAdapter)
如果不想在DataSet中存储DataAdapter,可在保存时重新创建适配器(需保证查询语句与加载时完全一致):
public void SaveAddressChangesData(DataSet dataSet, int id) { try { var changes = dataSet.GetChanges(); if (changes == null) return; using var conn = new MySqlConnection(DB.connString); // 必须与AddressListDataSet中的查询语句一致 var adapter = new MySqlDataAdapter($"SELECT * FROM your_table WHERE id = {id}", conn); var builder = new MySqlCommandBuilder(adapter); adapter.Update(changes); dataSet.AcceptChanges(); } catch (Exception ex) { dataSet.RejectChanges(); } }
额外优化建议
- 避免直接在DataGridView中绑定DataSet的DefaultView,可考虑使用BindingSource作为中间层,提升数据操作的灵活性。
- 数据层的查询语句建议使用参数化查询,避免SQL注入风险:
var adapter = new MySqlDataAdapter("SELECT * FROM your_table WHERE id = @Id", conn); adapter.SelectCommand.Parameters.AddWithValue("@Id", id);
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

