You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms:如何将DataGridView的DataSet变更从UI层传至数据层

问题分析与解决方案

核心错误原因

  1. UI层实例不一致:窗体中PartnerRepository属性每次被访问都会创建新的PartnerRepository实例。加载数据时用的是实例A,保存时用的是实例B,实例B的ds、adapter字段完全未初始化,调用SaveAddressChangesData时触发NullReferenceException。
  2. 数据层有状态设计缺陷:依赖类级别的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:03:08