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

WPF项目中ExecuteReader为何自动关闭?求助排查

问题分析与解决方案

1. SqlDataReader 莫名关闭的核心原因

你的GetAll()方法用yield return实现延迟枚举,同时结合了单例模式的数据库连接,这是问题的根源:

  • 调用GetAll().Any(...)时,枚举只会执行到找到第一个匹配项就停止,此时迭代器的using块会触发SqlConnection.Dispose(),直接关闭了单例连接。
  • 后续再次调用GetAll()时,拿到的是已被Dispose的连接实例,执行ExecuteReader必然失败,表现为DataReader莫名关闭。
  • 另外你注释掉了conn.Open(),虽然ExecuteReader会自动打开连接,但如果连接已被Dispose,自动打开逻辑也会失效。

2. 数据库连接未关闭的问题

单例连接模式本身就容易引发连接泄漏:一旦操作提前终止或连接状态异常,就会出现连接未正确释放的情况。ADO.NET自带连接池,完全不需要用单例复用连接,重复创建连接实例不会有性能损耗。


修复代码

修正GetAll()方法

放弃单例连接,每次创建新的连接实例,同时确保连接正确管理:

public IEnumerable<Problem> GetAll()
{
    // 每次创建新连接,依赖ADO.NET连接池复用底层连接
    using (SqlConnection conn = new SqlConnection(DatabaseSingleton.GetConnectionString()))
    {
        conn.Open();
        using (SqlCommand command = new SqlCommand("SELECT * FROM Problem", conn))
        {
            using (SqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return new Problem
                    {
                        Id = reader.GetInt32(0),
                        NameOfAlert = reader.GetString(1),
                        Value = Enum.Parse<Value>(reader[2].ToString()),
                        Result = Enum.Parse<Result>(reader[3].ToString()),
                        Message_Id = reader.GetString(4)
                    };
                }
            }
        }
    }
}

注:修改DatabaseSingleton,让它返回连接字符串而非连接实例,这是数据库操作的标准用法。

优化重复数据校验逻辑

原代码每次循环都全表查询GetAll().Any(),性能极差。直接在数据库层面判断是否存在目标数据,大幅减少IO开销:

// 新增判断Problem是否存在的方法
public bool ProblemExists(string messageId)
{
    using (SqlConnection conn = new SqlConnection(DatabaseSingleton.GetConnectionString()))
    {
        conn.Open();
        using (SqlCommand command = new SqlCommand("SELECT COUNT(1) FROM Problem WHERE Message_Id = @MessageId", conn))
        {
            command.Parameters.AddWithValue("@MessageId", messageId);
            return (int)command.ExecuteScalar() > 0;
        }
    }
}

// 同理新增AlertExists方法
public bool AlertExists(string messageId)
{
    using (SqlConnection conn = new SqlConnection(DatabaseSingleton.GetConnectionString()))
    {
        conn.Open();
        using (SqlCommand command = new SqlCommand("SELECT COUNT(1) FROM Alert WHERE Id_MimeMessage = @MessageId", conn))
        {
            command.Parameters.AddWithValue("@MessageId", messageId);
            return (int)command.ExecuteScalar() > 0;
        }
    }
}

修正MailKitLib方法

用新增的存在判断方法替代全表查询,同时修正连接和客户端断开逻辑:

public static void MailKitLib(EmailParser emailParser)
{
    bool help = true;
    
    do
    {
        using (var client = new ImapClient())
        {
            using (var cancel = new System.Threading.CancellationTokenSource())
            {
                client.Connect(emailParser.ServerName, emailParser.Port, emailParser.IsSSLuse, cancel.Token);
                client.Authenticate(emailParser.Username, emailParser.Password, cancel.Token);
    
                var inbox = client.Inbox;
                inbox.Open(FolderAccess.ReadOnly, cancel.Token);
    
                // 仅保存原始数据
                for (int i = 0; i < inbox.Count; i++)
                {
                    var message = inbox.GetMessage(i, cancel.Token);
                    GetBodyText = message.TextBody;
                    string messageId = message.MessageId;
                    
                    if (!dAOProblem.ProblemExists(messageId))
                    {
                        Problem problem = new Problem(messageId);
                        dAOProblem.Save(problem);
                        
                        if (!dAOAlert.AlertExists(messageId))
                        {
                            Alert alert = new Alert(messageId, message.Date.DateTime, message.From.ToString(), 1, problem.Id);
                            dAOAlert.Save(alert);
                        }
                    }
                }
                
                client.Disconnect(true, cancel.Token);
            }
        }
    } while (help);
}

额外优化建议

  • MailKit遍历邮件时,inbox.Count可能在遍历过程中变化,建议改用inbox.Fetch批量获取消息摘要,或直接foreach遍历inbox。
  • 枚举解析用Enum.TryParse替代Enum.Parse,避免格式错误导致的崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 13:15:07