qq_41945416 2025-11-12 01:56 采纳率: 90.6%
浏览 6
已结题

关于在mfc中使用ado可能发生断线,需要断线重连的问题

在mfc+ado+sqlserver的编程项目,某信息管理系统当中,有类CADO负责 使用ado将mfc和sqlserver联系起来进行编程和使用,并负责收集,显示,和保存ado出错的信息,比如_com_error m_error 就是CADO类的成员变量。
我的CADO类里封装了很多有关于数据库连接和记录集的操作。特别有成员函数CADO::RstAddNew();是对ado记录集里的AddNew();方法进行的包装。同理可得,我还包装了CAD::RstUpdate(); CADO::RstClose();等方法。
但是实际使用时候 用户端可能因为各种原因导致网络连接发生不通畅,断开,甚至是连接被改变。比如说WIFI信号弱,或者网线氧化接触不良,或者网络拓扑结构发生改变,或者笔记本电脑睡眠后启动等等情况,如果这个时候再去使用mfc和ado,只要是和数据库有关系的操作就会报如下错误。
[CADOERROR]Microsoft OLE DB Provider for SQL Server
[错误信息] [DBNETLIB][ConnectionWrite (send()).]一般性网络错误。请检查网络文档。
[错误代码] 未指定的错误
[HRESULT] 0x80004005
[错误提示]读取数据库发生错误!
我的问题是 1.1 如何给项目添加带有 端口号 服务器名称,用户名,密码 和 数据库名称的 对话框,以更新ado连接?
1.2 因为我想的是这个断线重连的对话框不能和CADO类有任何绑定的关系,即我不希望出现CADO就是断线重连对话框的类的情况。那如果是这样的话,就只有在调用我封装的CADO成员函数时候捕获错误进行实现了。
但问题是 我已经在封装的时候 使用了try{……}catch(_com_error * e){……} 已经进行捕获错误了。
我在调用的时候,还能二次捕获错误吗?就像如下代码。


void CDemoDlg::OnBnClickedButton1()
{
  try
{
      g_app_ado.RstAddNew(); //CADO g_app_ado 是全局的,已事先定义清楚。
}
catch(_com_error * e)
{
 // 对封装后的成员函数仍然捕获错误
//以便从这里根据错误情况再显示断线重连对话框
}
}



1.3 假设用户已经通过断线重连对话框更新了ado连接,那么之前出错的ado封装函数还没有执行到位,需要重新执行,应该通过哪条命令或函数,对try catch 里出错的语句 重新进行执行?

  • 写回答

3条回答 默认 最新

  • 宝码香车 优质创作者: JavaScript技术领域 2025-11-12 10:34
    关注

    让【宝码香车】来帮您解答,本回答参考DeepSeek编写,并整理提供。

    如果答案能帮助您,请采纳,非常感谢!
    .
    每日一句正能量,让它带给您好心情:生活中的每一个小进步,都值得被庆祝。
    .

    针对您在MFC项目中使用ADO时遇到的断线重连问题,我将逐一解答您的疑问。

    1.1 如何添加连接配置对话框

    您可以创建一个独立的配置对话框类来管理ADO连接参数,这个对话框与CADO类完全解耦。

    创建对话框类CConnectConfigDlg

    // ConnectConfigDlg.h
    class CConnectConfigDlg : public CDialogEx
    {
        DECLARE_DYNAMIC(CConnectConfigDlg)
    public:
        CConnectConfigDlg(CWnd* pParent = nullptr);
        virtual ~CConnectConfigDlg();
    
    
        CString m_strServer;
        CString m_strPort;
        CString m_strDatabase;
        CString m_strUsername;
        CString m_strPassword;
    
    
    protected:
        virtual void DoDataExchange(CDataExchange* pDX);
    
    
        DECLARE_MESSAGE_MAP()
    public:
        virtual BOOL OnInitDialog();
    };
    
    // ConnectConfigDlg.cpp
    CConnectConfigDlg::CConnectConfigDlg(CWnd* pParent /*=nullptr*/)
        : CDialogEx(IDD_CONNECT_CONFIG_DIALOG, pParent)
        , m_strServer(_T(""))
        , m_strPort(_T("1433"))
        , m_strDatabase(_T(""))
        , m_strUsername(_T(""))
        , m_strPassword(_T(""))
    {
    }
    
    
    void CConnectConfigDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialogEx::DoDataExchange(pDX);
        DDX_Text(pDX, IDC_EDIT_SERVER, m_strServer);
        DDX_Text(pDX, IDC_EDIT_PORT, m_strPort);
        DDX_Text(pDX, IDC_EDIT_DATABASE, m_strDatabase);
        DDX_Text(pDX, IDC_EDIT_USERNAME, m_strUsername);
        DDX_Text(pDX, IDC_EDIT_PASSWORD, m_strPassword);
    }
    
    
    BOOL CConnectConfigDlg::OnInitDialog()
    {
        CDialogEx::OnInitDialog();
        // 可以从注册表或配置文件中加载上次的连接设置
        return TRUE;
    }
    

    CADO类中添加重新连接的方法:

    // CADO.h
    class CADO
    {
    public:
        BOOL Reconnect(LPCTSTR server, LPCTSTR port, LPCTSTR database, 
                       LPCTSTR username, LPCTSTR password);
        BOOL IsConnectionValid();
        
    private:
        _ConnectionPtr m_pConnection;
        _com_error m_error;
    };
    
    // CADO.cpp
    BOOL CADO::Reconnect(LPCTSTR server, LPCTSTR port, LPCTSTR database, 
                         LPCTSTR username, LPCTSTR password)
    {
        try {
            if (m_pConnection != NULL && m_pConnection->State == adStateOpen) {
                m_pConnection->Close();
            }
            
            CString strConnection;
            strConnection.Format(_T("Provider=SQLOLEDB.1;Data Source=%s,%s;Initial Catalog=%s;User ID=%s;Password=%s"),
                server, port, database, username, password);
            
            m_pConnection.CreateInstance(__uuidof(Connection));
            m_pConnection->Open((_bstr_t)strConnection, "", "", adConnectUnspecified);
            
            return TRUE;
        }
        catch (_com_error &e) {
            m_error = e;
            return FALSE;
        }
    }
    
    
    BOOL CADO::IsConnectionValid()
    {
        try {
            if (m_pConnection == NULL || m_pConnection->State != adStateOpen) {
                return FALSE;
            }
            
            // 执行一个简单的测试查询
            _RecordsetPtr pRst = NULL;
            pRst = m_pConnection->Execute("SELECT 1", NULL, adCmdText);
            return TRUE;
        }
        catch (_com_error &e) {
            m_error = e;
            return FALSE;
        }
    }
    

    1.2 关于错误捕获的问题

    您可以在调用封装函数时进行二次错误捕获,这是完全可行的。ADO错误通常通过_com_error异常抛出,可以在不同层级进行捕获。

    修改后的调用代码:

    void CDemoDlg::OnBnClickedButton1()
    {
        int nRetryCount = 0;
        const int MAX_RETRY = 2;
        
        while (nRetryCount <= MAX_RETRY) {
            try {
                g_app_ado.RstAddNew();
                break; // 成功执行,跳出循环
            }
            catch (_com_error &e) {  // 注意:这里应该是引用,不是指针
                nRetryCount++;
                
                // 检查是否是网络连接错误
                if (IsNetworkError(e) && nRetryCount <= MAX_RETRY) {
                    if (HandleConnectionError() == IDOK) {
                        // 用户成功重新连接,继续重试
                        continue;
                    } else {
                        // 用户取消重连
                        break;
                    }
                } else {
                    // 其他类型的错误或重试次数已用完
                    CString strError;
                    strError.Format(_T("操作失败,错误:%s"), (LPCTSTR)e.ErrorMessage());
                    AfxMessageBox(strError);
                    break;
                }
            }
        }
    }
    
    
    BOOL CDemoDlg::IsNetworkError(_com_error &e)
    {
        HRESULT hr = e.Error();
        // 这些是常见的连接相关错误代码
        return (hr == 0x80004005 ||  // 未指定的错误
                hr == 0x80040E14 ||  // 无法连接到服务器
                hr == 0x80040E19 ||  // 无法打开连接
                hr == 0x80040E21 ||  // 多步操作产生错误
                hr == 0x80040E35);   // 事务中的连接无法被枚举
    }
    
    
    int CDemoDlg::HandleConnectionError()
    {
        CConnectConfigDlg dlg;
        
        // 可以在这里设置默认值或从配置加载
        if (dlg.DoModal() == IDOK) {
            if (g_app_ado.Reconnect(dlg.m_strServer, dlg.m_strPort, 
                                   dlg.m_strDatabase, dlg.m_strUsername, 
                                   dlg.m_strPassword)) {
                AfxMessageBox(_T("重新连接成功!"));
                return IDOK;
            } else {
                AfxMessageBox(_T("重新连接失败,请检查连接参数!"));
                return IDCANCEL;
            }
        }
        return IDCANCEL;
    }
    

    1.3 重新执行出错的操作

    对于需要重新执行的操作,建议使用重试机制。您需要在捕获到连接错误后,在用户成功重新连接后重新调用原来的方法。

    改进的重试机制实现:

    // 通用的数据库操作执行函数
    template<typename Func, typename... Args>
    BOOL ExecuteWithRetry(Func&& func, Args&&... args)
    {
        int nRetryCount = 0;
        const int MAX_RETRY = 3;
        
        while (nRetryCount <= MAX_RETRY) {
            try {
                // 执行传入的函数
                std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
                return TRUE;
            }
            catch (_com_error &e) {
                nRetryCount++;
                
                if (IsNetworkError(e) && nRetryCount <= MAX_RETRY) {
                    CConnectConfigDlg dlg;
                    if (dlg.DoModal() == IDOK) {
                        if (g_app_ado.Reconnect(dlg.m_strServer, dlg.m_strPort, 
                                               dlg.m_strDatabase, dlg.m_strUsername, 
                                               dlg.m_strPassword)) {
                            // 重新连接成功,继续下一次重试
                            continue;
                        }
                    }
                    // 重连失败或用户取消,退出重试
                    break;
                } else {
                    // 非网络错误或重试次数用完,直接抛出异常
                    throw;
                }
            }
        }
        return FALSE;
    }
    
    
    // 使用示例
    void CDemoDlg::OnBnClickedButton1()
    {
        auto operation = []() {
            g_app_ado.RstAddNew();
            // 可以在这里添加其他相关操作
            g_app_ado.RstUpdate();
        };
        
        if (!ExecuteWithRetry(operation)) {
            AfxMessageBox(_T("操作失败,请检查网络连接!"));
        } else {
            AfxMessageBox(_T("操作成功!"));
        }
    }
    

    更简单的实现方式(如果不想使用模板):

    BOOL CDemoDlg::ExecuteDatabaseOperation()
    {
        int nRetryCount = 0;
        const int MAX_RETRY = 3;
        
        while (nRetryCount <= MAX_RETRY) {
            try {
                // 这里是需要执行的数据库操作序列
                g_app_ado.RstAddNew();
                // 其他数据库操作...
                g_app_ado.RstUpdate();
                
                return TRUE; // 成功执行
            }
            catch (_com_error &e) {
                nRetryCount++;
                
                if (IsNetworkError(e) && nRetryCount <= MAX_RETRY) {
                    if (!ShowReconnectDialog()) {
                        return FALSE; // 用户取消重连
                    }
                    // 重连成功,继续下一次循环(重试操作)
                } else {
                    // 其他错误,显示错误信息
                    DisplayErrorMessage(e);
                    return FALSE;
                }
            }
        }
        return FALSE;
    }
    

    总结建议

    1. 连接检测:在执行重要操作前,可以先调用IsConnectionValid()检测连接状态
    2. 超时设置:在连接字符串中设置连接超时和命令超时
    3. 配置持久化:将成功的连接配置保存到注册表或配置文件中
    4. 自动重连:对于短暂网络中断,可以实现自动重连而不显示对话框
    5. 状态提示:在界面显示当前连接状态,让用户了解连接情况

    这种设计确保了CADO类与重连逻辑的解耦,同时提供了灵活的错误处理和重试机制。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 系统已结题 11月20日
  • 已采纳回答 11月12日
  • 修改了问题 11月12日
  • 创建了问题 11月12日