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

WinForm遍历数据表及外键关联填充实体数据失效问题

问题背景

我创建了两张数据表:Members 和 Contracts。

  • Contracts 表设置外键,关联 Members 表的ID字段。
  • 项目中定义了两个实体类:Member 和 Contract。
  • 项目主静态类 Club 维护了一个存储 Contract 对象的 ArrayList 集合,每个 Contract 实例都包含一个 Member 类型的字段。
    当前需要在 Club 类中实现逻辑:将数据库中所有 Contract 记录关联匹配正确的 Member 字段值后,填充到前述 ArrayList 集合中。
    但编写的 Club 类 Populate 方法运行异常,无法正常工作,故障代码如下:
public static void Populate()
        {
            string connStr = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Contribution;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
            SqlConnection con = new SqlConnection(connStr);
            SqlCommand cmd = new SqlCommand("SELECT * FROM Contracts", con);
            SqlDataReader dr;
            try
            {
                con.Open();
                dr = cmd.ExecuteReader();
                while(dr.Read())
                {
                    Member newMember = null;
                    int memberID = dr.GetInt32(dr.GetOrdinal("memberID"));
                    MessageBox.Show(memberID.ToString());
                    SqlCommand cmd2 = new SqlCommand("SELECT * FROM Members WHERE ID = @memberID", con);
                    cmd2.Parameters.AddWithValue("@memberID", memberID);
                    cmd2.ExecuteNonQuery();
                    SqlDataReader dr2;
                    dr2 = cmd2.ExecuteReader();
                    while (dr2.Read())
                    {
                        string name = dr2.GetString(dr2.GetOrdinal("name"));
                        DateTime birthDate = DateTime.ParseExact(dr2.GetString(dr2.GetOrdinal("birthDate")), "dd/MM/yyyy", null);
                        DateTime joinDate = DateTime.ParseExact(dr.GetString(dr.GetOrdinal("joinDate")), "dd/MM/yyyy", null);
                        bool isPlaying = (dr2.GetInt32(dr2.GetOrdinal("isPlaying")) == 1 ? true : false);
                        newMember = new Member(name, birthDate, joinDate, isPlaying);
                        MessageBox.Show(name);
                    }
                    dr2.Close();
                    Club.AddContract(new Contract(newMember));
                }
                dr.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                con.Close();
            }
        }
故障现象

测试运行未得到预期结果:

  • 运行后无有效数据返回
  • 实现的计算总缴费额、获取最年轻会员等方法,相关字段取值均为空或null
  • 已做调整:改用参数化查询、修复部分已知小问题,但故障仍然存在。
故障原因与修复方案

代码有3个直接导致数据读取失败的硬错误,按优先级修复即可:

  1. 连接被占用导致第二个查询无法执行
    在第一个SqlDataReader(变量dr)未关闭的情况下,复用同一个SqlConnection执行第二个查询,而SQL Server默认关闭MARS(多活动结果集)功能,这种操作会直接抛出连接占用异常,这是拿不到Member数据的核心原因。

两种修复方案选其一即可:

  • 推荐方案:先一次性把所有Contracts关联的memberID读进内存集合,关闭第一个reader之后,再遍历集合逐个查询关联的Member信息,没有额外配置成本,也不容易出隐式bug
  • 备选方案:在连接字符串中添加MultipleActiveResultSets=True开启MARS功能,不推荐新手使用,容易出现未释放的reader长期占用连接的问题
  1. 读取joinDate字段用错了DataReader实例
    代码中读取joinDate时写的是dr.GetOrdinal("joinDate"),用的是外层读取Contracts表的dr对象,不是读取Members表的dr2对象,这里会直接抛出“列不存在”的异常,或者读取到错误的值,改成dr2.GetOrdinal("joinDate")即可。
  2. 存在多余的无效查询调用
    在执行cmd2的成员查询之前,多写了一行cmd2.ExecuteNonQuery(),这个方法仅用于执行增/删/改操作,执行SELECT语句时调用这个方法不会返回结果集,平白多执行一次无效查询,直接删除这行即可。

修复后可运行参考代码

public static void Populate()
{
    string connStr = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Contribution;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
    List<int> contractMemberIds = new List<int>();
    
    // using块自动释放连接、命令、读取器资源,不用手动写Close,避免资源泄漏
    using (SqlConnection con = new SqlConnection(connStr))
    {
        con.Open();
        // 第一步:先读取所有合同关联的会员ID
        SqlCommand cmd = new SqlCommand("SELECT memberID FROM Contracts", con);
        using (SqlDataReader dr = cmd.ExecuteReader())
        {
            while (dr.Read())
            {
                contractMemberIds.Add(dr.GetInt32(dr.GetOrdinal("memberID")));
            }
        } // 第一个reader在这里自动释放,连接不再被占用

        // 第二步:逐个查询会员信息,生成Contract对象
        foreach (int memberID in contractMemberIds)
        {
            Member newMember = null;
            SqlCommand cmd2 = new SqlCommand("SELECT * FROM Members WHERE ID = @memberID", con);
            cmd2.Parameters.AddWithValue("@memberID", memberID);
            
            using (SqlDataReader dr2 = cmd2.ExecuteReader())
            {
                while (dr2.Read())
                {
                    string name = dr2.GetString(dr2.GetOrdinal("name"));
                    DateTime birthDate = DateTime.ParseExact(dr2.GetString(dr2.GetOrdinal("birthDate")), "dd/MM/yyyy", null);
                    // 修复:使用dr2读取joinDate字段
                    DateTime joinDate = DateTime.ParseExact(dr2.GetString(dr2.GetOrdinal("joinDate")), "dd/MM/yyyy", null);
                    bool isPlaying = dr2.GetInt32(dr2.GetOrdinal("isPlaying")) == 1;
                    newMember = new Member(name, birthDate, joinDate, isPlaying);
                }
            }
            Club.AddContract(new Contract(newMember));
        }
    }
}

额外优化建议

  • 如果数据量不大,直接写联表SQL一次读完全部数据即可,不用循环查数据库,性能更高也不会出连接占用问题:
    SELECT m.name, m.birthDate, m.joinDate, m.isPlaying 
    FROM Contracts c 
    LEFT JOIN Members m ON c.memberID = m.ID
    
  • 如果Members表中birthDate、joinDate字段本身就是SQL Server的datetime/date类型,不要用GetString再做日期解析,直接调用dr2.GetDateTime(列索引)读取即可,能避免日期格式不匹配导致的解析报错。
  • 不推荐用ArrayList存储对象,建议改用泛型集合List<Contract>,可以避免装箱拆箱的性能损耗,也不用做强制类型转换。

内容的提问来源于stack exchange,提问作者user14579575

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:21:29