Visual Studio 2019を使ってC++のコンソールアプリケーションを開発中なのですが、  
MySQLでデータ管理をすることとなり開発環境を整えていたのですがうまくいきません。  
どなたか教えてもらえないでしょうか？宜しくお願い致します。

追記：  
VS2019でMySQLを利用したソフトを開発したく、”Connector / NET 8.0.20”と”MySQL for Visual Studio
1.2.9”をインストールしたのですが、参照マネージャーにも表示されません。  
ネットで色々探したのですがうまくいきません。VS2019でMySQLを利用したソフトを開発されてる方がいらっしゃいましたら環境設定方法を教えていただけませんでしょうか、宜しくおねがいします。

**開発環境**  
C++  
Visual Studio 2019  
Windows Server 2016  
XAMPP
CC BY-SA 4.0
2020-05-06T14:47:25.133
66354
2020-06-06T13:54:39.390
2020-05-07T01:12:52.103
3060
40013
question
c++ mysql visual-studio
VisualStudio2019C++を使ってMySQLの開発
アプリケーションを開発する際には

  1. どのような環境で実行するのかを決定する
  2. 実行環境を踏まえてどのような環境で開発するのかを決定する
  3. 開発を行う

という手順になるはずです。1.の実行環境はどのような想定なのでしょうか？ それがない限り第三者が2.を指南することはできません。

なお、[Connectors and APIs](https://dev.mysql.com/doc/index-
connectors.html)で示されていますが、

  * Connector/NETを使用するのであればC#言語が必要
  * C++言語を使用するのであればConnector/C++が必要

であり、質問文の組み合わせはそもそも矛盾しています。
CC BY-SA 4.0
2020-05-07T00:29:26.473
66362
2020-05-07T00:29:26.473
4236
66354
answer
まいどだうも。おせわになります。

横槍失礼。少し混ぜてくださいネ（笑）。

結論から言って MySQL は ADO 経由でアクセスできるので、  
先ずは Excel VBA 辺りの簡易な環境で  
簡単なアクセス処理を作ってみることをお勧めしておきます。  
（アプローチ的な話ですね）

Excel VBA からMySQLへ接続する参考サイトは以下の通り。  
データベース（MySQL）に接続する（ADO）  
<https://excelwork.info/excel/databasemysql/>

ODBC 版の接続文字列の概要は以下の通り。

```

    Driver={MySQL ODBC 8.0 Unicode Driver};PORT=3306;SERVER=127.0.0.1;DATABASE=;UID=root;PWD=;
    
```

※中括弧は省略せずに書いてください。  
※DATABASE= には CREATE DATABASE コマンドで構築に使用した名前を指定。  
※UID、PWDにもサーバー構築で使用したユーザー名とパスワードを指定してください。

まずはサーバー側の環境構築を行ってから、Excel VBA を使って、接続テストしてみてから、  
Visual Studio 2019 の開発をやってみるのもよいかもしれません。

Excel VBA 環境では、加えてサーバー側の問題か、  
クライアント開発環境側の切り分けもできます。

例えば、Excel VBA でもアクセスできないのであれば、  
サーバー側の設定に問題があります。

さて、 ohwhsmm7.blog.fc2.com/blog-entry-520.html というページも見ましたが。  
C++/CLI + Boostのミックスライブラリですね。

開発環境の構築と設定がメンドクサそうです。

ちょっと検索してみると、

C++ ADO operates mysql database  
<https://programming.vip/docs/c-ado-operates-mysql-database.html>

なんていうのがあります。

ADO を経由して ODBC 接続してます。  
このサイトの方法では ODBC マネジャーを使って DSN を設定しなければならないので、  
そういった意味でも環境作成がメンドクサそうです。  
先に説明した Excel VBA を利用する接続文字列で書き換えれば、DSN設定は要りません。

しかし、ADO 経由で ODBC 接続するということは、  
ODBCドライバ名と幾つかのパラメータがわかっていれば、  
MFC の CRecordset クラスの継承クラスを使って  
MySQL へのアクセスができるんじゃないのかと、  
やってみました。

まぁ、繋がりますね。  
問題は、レコードセットが、「ダイナセット」に対応しておらず、  
「スナップショット」で接続しにいかなければならない様です。

なお、OLE DB 接続では、別途 MySQL 用の OLE DB ドライバが必要な様でした。  
パパっと検索してみた限りでは、古いバージョンのものしかなかったので、  
ここでの説明は割愛します。

さて、MFCについては、ココの住人さん達には  
ドキュメントが更新されていなくてバグっている上に  
オワコンなので評判が悪いようですが、とっとと作るには、  
よいのではないでしょうか。  
（納品まで持っていくかどうかはメンテを考慮したほうがよいかもしれません）

<afxdb.h> に含まれるCRecordsetを中心とした  
一連の ODBC 用のDBアクセス・クラス群でのテンプレートの構築は  
MFCが使えれば、Visual Studio 2015 までであれば  
アプリケーション・ウィザードを利用することで  
構築できます。

また、Visual Studio 2017 であれば、  
「MFC コンシューマ・ウィザード」があるので、  
テンプレートを作るのは比較的簡単です。

しかし、Visual Studio 2019 では、  
クラス・ウィザードを使って、  
イチからクラスを構築しなければならないのが、  
かなり面倒かもしれません。  
以前のバージョンでプロジェクトを構築してから  
プロジェクトごと移行変換した方が楽かもしれません。

あと、CRecordset を使用したDBアクセサの構築は、  
精度の高い表示や丸めについては多少不利です。

DECIMAL 型やNumber 型 Currency 型 については、  
DoFieldExchange()のRFX_xxxx() マクロ群で double 型に丸められてしまうので、  
精度に問題が発生します。

もし、精度が double の精度の15桁以上必要であれば、先の  
「C++ ADO operates mysql database」  
サイトを参考にするか、  
<ole2.h> と<oleauto.h> の内容を理解した上で  
より精度の高い数値を扱える様に  
修正することをお勧めしておきます。

VARIANT 型は一応 96bit + 符号のDECIMAL 型の固定小数点型を保持でき、  
<oleauto.h> は、DECIMAL型と他の型との変換と演算用パッケージです。

こうやって見ると、  
ODBC レコードセット（MFC CRecordset の継承クラス）を使う場合と  
ODBC + ADO を使う場合、  
どちらも  
"Connector / NET 8.0.20"と"MySQL for Visual Studio 1.2.9"  
両方共に使わなくてもよい感じです。

ということで、どのような環境での開発をお求めかはやはり大切です。  
もし"Connector / NET 8.0.20"と"MySQL for Visual Studio 1.2.9"を使用した  
クライアントサイドのアクセス方法が知りたかったのであれば、  
この解答は、完全に外しているのでお詫び申し上げる次第です。

え？Win32で構築するって？鬼ですね。（笑）  
茨の道が待っていますが、できなくはないです。

とりあえずはまぁこんな感じで。

参考  
※プリコンパイルヘッダ内（pch.h / framework.h / stdafx.h の何れか）に  
以下の２行も追加してください

```

    #include <locale.h>
    #include <afxdb.h>
    
```

* * *

Project1.cpp

```

    // Project1.cpp : このファイルには 'main' 関数が含まれています。プログラム実行の開始と終了がそこで行われます。
    //
    
    #include "pch.h"
    //#include "framework.h"//←要らない
    #include "Project1.h"
    #include "Project1Set.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    
    // 唯一のアプリケーション オブジェクトです
    
    CWinApp theApp;
    
    using namespace std;
    
    int _tmain(int argc, LPTSTR argv[])
    {
        int nRetCode = 0;
    
        HMODULE hModule = ::GetModuleHandle(nullptr);
    
        if (hModule != nullptr)
        {
            // MFC を初期化して、エラーの場合は結果を出力する
            if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
            {
                // TODO: アプリケーションの動作を記述するコードをここに挿入してください。
                wprintf(L"致命的なエラー: MFC の初期化が失敗しました\n");
                nRetCode = 1;
            }
            else
            {
                // TODO: アプリケーションの動作を記述するコードをここに挿入してください。
                if (nullptr != _tsetlocale(LC_ALL, _T("japanese")))
                {
                    if (SUCCEEDED(CoInitialize(nullptr)))
                    {
                        CProject1Set cProject1Set;
                        BOOL bResult = FALSE;
                        try
                        {
                            bResult = cProject1Set.Open(-1);
                        }
                        catch (CDBException* ex)
                        {
                            LPCTSTR pValue = new TCHAR[1024];
                            if (nullptr != pValue)
                            {
                                if (ex->GetErrorMessage((LPTSTR)pValue, 1024))
                                {
                                    wprintf(L"%s\n", pValue);
                                }
                                delete[] pValue;
                                pValue = nullptr;
                            }
                        }
                        catch (...)
                        {
                        }
                        if (bResult)
                        {
                            for (UINT nCol = 0; nCol < cProject1Set.m_nFields; nCol++)
                            {
                                _tprintf(_T("%s,"), (LPCTSTR)cProject1Set.m_rgODBCFieldInfos[nCol].m_strName);
                            };
                            _tprintf(_T("\n"));
                            while (!cProject1Set.IsEOF())
                            {
                                for (UINT nCol = 0; nCol < cProject1Set.m_nFields; nCol++)
                                {
                                    switch (nCol)
                                    {
                                    case 0:
                                        _tprintf(_T("%.0f,"), cProject1Set.m_Title0 * 10000.0);
                                        break;
                                    case 1:
                                        _tprintf(_T("%04d/%02d/%02d %02d:%02d:%02d,"),
                                            cProject1Set.m_Title1.GetYear(),
                                            cProject1Set.m_Title1.GetMonth(),
                                            cProject1Set.m_Title1.GetDay(),
                                            cProject1Set.m_Title1.GetHour(),
                                            cProject1Set.m_Title1.GetMinute(),
                                            cProject1Set.m_Title1.GetSecond()
                                        );
                                        break;
                                    case 2:
                                        _tprintf(_T("%.0f,"), cProject1Set.m_Title2 * 10000.0);
                                        break;
                                    case 3:
                                        _tprintf(_T("%s,"), (LPCTSTR)cProject1Set.m_Title3);
                                        break;
                                    case 4:
                                        _tprintf(_T("%ld,"), cProject1Set.m_Title4);
                                        break;
                                    case 5:
                                        _tprintf(_T("%ld,"), cProject1Set.m_Title5);
                                        break;
                                    case 6:
                                        _tprintf(_T("%.0f,"), cProject1Set.m_Title6 * 10000.0);
                                        break;
                                    case 7:
                                        _tprintf(_T("%.0f,"), cProject1Set.m_Title7 * 10000.0);
                                        break;
                                    case 8:
                                        _tprintf(_T("%s,"), (LPCTSTR)cProject1Set.m_Title8);
                                        break;
                                    case 9:
                                        _tprintf(_T("%.0f,"), cProject1Set.m_Title9 * 10000.0);
                                        break;
                                    case 10:
                                        _tprintf(_T("%04d/%02d/%02d %02d:%02d:%02d,"), 
                                            cProject1Set.m_Title10.GetYear(),
                                            cProject1Set.m_Title10.GetMonth(),
                                            cProject1Set.m_Title10.GetDay(),
                                            cProject1Set.m_Title10.GetHour(),
                                            cProject1Set.m_Title10.GetMinute(),
                                            cProject1Set.m_Title10.GetSecond()
                                        );
                                        break;
                                    }
                                };
                                _tprintf(_T("\n"));
                                cProject1Set.MoveNext();
                            };
                            cProject1Set.Close();
                        }
                        else
                        {
                            wprintf(L"致命的なエラー: オープンエラー\n");
                            nRetCode = 1;
                        }
                        CoUninitialize();
                    }
                    else
                    {
                        wprintf(L"致命的なエラー: OLE の初期化に失敗しました。OLE ライブラリのバージョンが正しいことを確認してください。\n");
                        nRetCode = 1;
                    }
                }
                else
                {
                    wprintf(L"致命的なエラー: ロケール設定に失敗しました\n");
                    nRetCode = 1;
                }
            }
        }
        else
        {
            // TODO: 必要に応じてエラー コードを変更してください
            wprintf(L"致命的なエラー: GetModuleHandle が失敗しました\n");
            nRetCode = 1;
        }
    
        return nRetCode;
    }
    
```

* * *

Project1Set.h

```

    #pragma once
    class CProject1Set :
        public CRecordset
    {
    public:
        CProject1Set(CDatabase* pdb = nullptr);
        ~CProject1Set();
        virtual CString GetDefaultConnect();
        virtual CString GetDefaultSQL();
        virtual void DoFieldExchange(CFieldExchange* pFX);
        double       m_Title0;
        COleDateTime m_Title1;
        double       m_Title2;
        CStringW     m_Title3;
        long         m_Title4;
        long         m_Title5;
        float        m_Title6;
        double       m_Title7;
        CStringW     m_Title8;
        double       m_Title9;
        COleDateTime m_Title10;
    };
    
```

* * *

Project1Set.cpp

```

    #include "pch.h"
    #include "Project1Set.h"
    
    
    CProject1Set::CProject1Set(CDatabase*pdb)
        : CRecordset(pdb)
    {
        m_nFields = 11;
        m_nDefaultType = CRecordset::OpenType::snapshot;
        m_Title0 = 0.0;
        m_Title2 = 0.0;
        m_Title3 = L"";
        m_Title4 = 0L;
        m_Title5 = 0L;
        m_Title6 = 1.0f;
        m_Title7 = 0.0;
        m_Title8 = L"";
        m_Title9 = 0.0;
    }
    
    
    CProject1Set::~CProject1Set()
    {
        if (IsOpen())
        {
            Close();
        }
    }
    
    
    CString CProject1Set::GetDefaultConnect()
    {
        // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
        return _T("Driver={MySQL ODBC 8.0 Unicode Driver};DATABASE=database1;PORT=3306;SERVER=127.0.0.1;UID=root;PWD=;");
    }
    
    
    CString CProject1Set::GetDefaultSQL()
    {
        // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
        return _T("select * from database1.table1");
    }
    
    
    void CProject1Set::DoFieldExchange(CFieldExchange* pFX)
    {
        // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
        CRecordset::DoFieldExchange(pFX);
        pFX->SetFieldType(CFieldExchange::FieldType::outputColumn);
        RFX_Double(pFX, _T("CustomerCode"), m_Title0);
        RFX_Date  (pFX, _T("SellDateTime"), m_Title1);
        RFX_Double(pFX, _T("ProductCode"),  m_Title2);
        RFX_Text  (pFX, _T("ProductName"),  m_Title3);
        RFX_Long  (pFX, _T("Price"),        m_Title4);
        RFX_Long  (pFX, _T("Count"),        m_Title5);
        RFX_Single(pFX, _T("Rate"),         m_Title6);
        RFX_Double(pFX, _T("Sum"),          m_Title7);
        RFX_Text  (pFX, _T("Remarks"),      m_Title8);
        RFX_Double(pFX, _T("StaffCode"),    m_Title9);
        RFX_Date  (pFX, _T("ModifyDate"),   m_Title10);
    }
    
```

* * *

結果：

CustomerCode,SellDateTime,ProductCode,ProductName,Price,Count,Rate,Sum,Remarks,StaffCode,ModifyDate,  
1312500000000,2020/05/30 10:08:00,4918200000,みかん,58,12,11000,7660000,1
箱,202027010000,2020/05/31 17:35:02,  
1312500000000,2020/05/30 10:08:00,4918200000,りんご,158,12,11000,20860000,1
箱,202027010000,2020/05/31 17:35:02,  
続行するには何かキーを押してください . . .

* * *

ではまた。
CC BY-SA 4.0
2020-05-29T21:37:12.470
67128
2020-05-30T01:53:26.957
2020-05-30T01:53:26.957
40393
40393
66354
answer
mysql-connector-c++-8.0.20を使ってこんな感じでやってみました。  
問題があればご指導お願いします。

```

    int Csql::putExecute(char message[255])
    {
        using namespace std;
    
        try {
    
            char buff[255];
    
            // ドライバーを定義
            sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
            // 
            unique_ptr<sql::Connection> con(driver->connect(HOST, USER, PASSWORD));
            //
            unique_ptr<sql::Statement> stmt(con->createStatement());
    
            // データベースを移動
            sprintf_s(buff, "USE %s", DATABASE.c_str());
            stmt->execute(buff);
    
            stmt->execute(message);
    
        }
        catch (sql::SQLException& e) {
            cout << "# ERR: SQLException in " << __FILE__ << " on line " << __LINE__ << endl;
            cout << "# ERR: " << e.what() << endl;
            cout << " (MySQL error code: " << e.getErrorCode();
            cout << ", SQLState: " << e.getSQLState() << " )" << endl;
            return EXIT_FAILURE;
        }
        catch (runtime_error& e) {
            cout << "# ERR: runtime_error in " << __FILE__ << " on line " << __LINE__ << endl;
            cout << "# ERR: " << e.what() << endl;
            return EXIT_FAILURE;
        }
    
        return 0;
    }
    
```
CC BY-SA 4.0
2020-05-31T12:11:50.537
67166
2020-05-31T12:29:11.317
2020-05-31T12:29:11.317
3060
40013
66354
answer
さて。困りました。  
＞問題があればご指導お願いします。  
とのことですが。。。

「納期」という言葉が出てくるということは、  
お客様か元受け様がおられると思いますので、  
そちらと **詳細設計を詰める** ことをおススメしておきます。

うがった見方かもしれませんが、お客様としては、  
調査力でなく調整力を測るために、わざと丸投げ放置しているような感もあります。  
「求めよ。さらば与えられん」も、良いですが、  
答えを求める先を間違っている様にも思います。

その上で問題点ですが。  
おそらく、ソースから推測するに、  
DBテーブルへ追加もしくは更新しもしくは削除したいのだと思いますが、  
更新プロセスについて、全く考慮されていません。

杞憂であればよいのですが、  
プログラム構築戦術以前に、バックエンドの戦略が  
全く理解できていない様にお見受けできます。

* * *

考慮可能な問題点について。(一部)

・mysql-connector-c++ の driver->connect()はスレッドセーフではありません。多重起動について考慮してください。  
・MFCのDBアクセスとdriver->connect()とは混在しないでください。  
後述のロックに問題が出たり、ロックを解除しない限り二度と接続できなくなったりするかもしれません。  
例えば、MFCでスナップショットを読み込んで、mysql-connector-c++で１行づつ書き出すなどということは止めてください。  
「すべてMFCで読み書きする」もしくは「すべてmysql-connector-c++ で読み書きする」どちらか一方にしてください。  
・MFCの読み込み処理はSQLインジェクション対応を考慮してください。(user40393さんのソース)  
◎レコードの変更前にトランザクションを開始してください。  
MFC ODBC の CDatabase を使う場合は、 追加/更新する前に更新開始の宣言も併せて行う必要があります。  
◎処理成功時、コミットしてください。  
◎多行同時更新について考慮してください。  
このままだと、テーブルの多行同時更新を検出できない上に、多行同時更新を検出できたとしても元に戻すことはできません  
◎エラーもしくは多行同時更新された時点でロールバックしてください。  
◎そもそもC#ではないのですから接続オブジェクトは使用が終わったら削除してください。  
理解しているとは思いますが delete だけではダメでしょう。  
◎接続していますが切断していません。切断については、エラー時などにも確実に切断出来る様に考慮してください。  
・更新処理についても 読み込み処理同様、SQLインジェクションについて考慮してください。  
・このままだと同一レコードに２重に書き込んだ場合、後勝ちになると思われますので、多重書き込みについて考慮してください。  
・「USE」コマンドの発行は他のプロセスで別のUSEが宣言された場合、どの様な結果になるか保証できないので、避けてください。  
・テーブルアクセスについては、「database名.table名」をご利用ください。  
・sprintf_s() はロケールの設定が必要です。  
・sprintf_s() の時点で キャラクタセットは 「UNICODE」 ではなく、 「CP932」 か「SJIS」か「UTF8」ですか。ご確認ください。  
Visual Studio
は、デフォルトで「UNICODE」なので、DBと合っていないと、文字化けや文字切れの原因になったりもするので、確認をお勧めします。  
・sprintf_s() はエラーを返す場合があります。エラーを返した場合を考慮してください。（エラー状態で execute すると...）  
・sprintf_s() ではなく、std::string の使用をお勧めしておきます。  
・更新作業中に別のプロセスからの更新を排除するために、多重アクセス防止のためテーブルロックやレコードロック等のロックについても考慮してください。  
（先の「多重起動」や「多行同時更新」「更新の後勝ち問題」とは別の課題）  
・デッドロックについても考慮が必要かもしれません。  
・ロックを行う場合は、ロックからの復帰の手順も併せて考慮してください。

## ・他、不足分についても考慮してください。（たとえば、MFCとmysql-connector-c++の両方を諦めて ADO や ADO.NET
など、他の ODBC アクセスライブラリを利用する等）

SQLインジェクション対策として、コマンドオブジェクトを利用するのであれば、  
MFC では、古いオブジェクトということもあり、コマンド・オブジェクトをサポートしていません。  
コマンド・オブジェクトを利用しないSQLインジェクション対策が取れるのであれば、  
MFCでもmysql-connector-c++でも、どちらでも構いません。

コマンド・オブジェクトを利用するには、  
ADOか、C++/CLIでADO.NETにすべきかもしれません。

場合にも依りますが、私なら、「SQL文の UPDATE コマンドに依る execute ステートメントに依るレコードの更新」ではなく、  
「ODBC の update ステートメント」で、カレントレコードを更新する方法を取ると思います。

ということで mysql-connector-c++で「SQLインジェクション対策が取れない」もしくは  
「現在レコードの更新がサポートされていない」のであれば、  
まさかの「ADO ですべて書き直し」ですか。。。  
（そんな訳は無いと思いたいです。）

* * *

おまけとして、打ち合わせ用のフローチャートも追記しておきます。  
本来なら画像でも貼るべきなのかもしれませんが、  
NS図で起こしてありますので  
必要に応じて書き換えて、お客様との調整にでもご利用ください。

```

    ┌────────────────────────┐
    │接続　　　　　　　　　　　　　　　　　　　　　　│
    ├────────────────────────┤
    │接続成功判定　　　　　　　　　　　　　　　　　　│
    │　┌──────────────────────┤
    │　│読み込みレコードセットオープン　　　　　　　│
    │　├──────────────────────┤
    │　│読み込みレコードセットオープン成功判定　　　│
    │　│　┌────────────────────┤
    │　│　│読み込みレコードを行末までループ　　　　│
    │　│　│　┌──────────────────┤
    │　│　│　│トランザクション開始　　　　　　　　│
    │　│　│　├──────────────────┤
    │　│　│　│トランザクション開始成功判定　　　　│
    │　│　│　│　┌────────────────┤
    │　│　│　│　│レコード更新開始宣言　　　　　　│
    │　│　│　│　├────────────────┤
    │　│　│　│　│（フィールド更新）　　　　　　　│
    │　│　│　│　├────────────────┤
    │　│　│　│　│レコード更新　　　　　　　　　　│
    │　│　│　│　├────────────────┤
    │　│　│　│　│レコード更新成功判定　　　　　　│
    │　│　│　│　│　┌──────────────┤
    │　│　│　│　│　│コミット　　　　　　　　　　│
    │　│　│　│　│　└──────────────┤
    │　│　│　│　│多行更新もしくは失敗　　　　　　│
    │　│　│　│　│　┌──────────────┤
    │　│　│　│　│　│ロールバック　　　　　　　　│
    │　│　│　│　└─┴──────────────┤
    │　│　│　│トランザクション開始失敗（ロック中）│
    │　│　│　│　┌────────────────┤
    │　│　│　│　│トランザクション開始失敗処理　　│
    │　│　│　├─┴────────────────┤
    │　│　│　│次レコードへ移動　　　　　　　　　　│
    │　│　│　└──────────────────┤
    │　│　│(loop)　　　　　　　　　　　　　　　　　│
    │　│　├────────────────────┤
    │　│　│読み込みレコードセットクローズ　　　　　│
    │　│　└────────────────────┤
    │　│レコードセットオープン失敗　　　　　　　　　│
    │　│　┌────────────────────┤
    │　│　│レコードセットオープン失敗時の処理　　　│
    │　├─┴────────────────────┤
    │　│切断　　　　　　　　　　　　　　　　　　　　│
    │　└──────────────────────┤
    │接続失敗　　　　　　　　　　　　　　　　　　　　│
    │　┌──────────────────────┤
    │　│接続失敗時の処理　　　　　　　　　　　　　　│
    └─┴──────────────────────┘
    
```

メモ帳など、FIXEDフォントでズレがなくなると思います。  
３の倍数フォント「9pt/12pt/15pt/18pt」などで利用してみてください。

上から８項目目の[レコード更新開始宣言]については、MFC では必要ですが、  
ADO では要りません。

このチャートで示した方法は複数レコードを１行ずつ操作し書き換えています。  
エラーなど操作に失敗した場合、現在処理中のレコードのみ  
ロールバックすることを想定しています。

エラーが起こった場合、接続してから切断するまでの  
すべてのレコード元にもどしたいのであれば、  
組み換えが必要になります。

* * *

もちろん考慮した結果、対策しないという選択もあるかもしれませんが、  
考慮の過程をこの掲示板に求めないでください。

また、仕様の結果についてもこの掲示板に求めないでください。

また、言うまでも無いとは思いますが、  
仕様をこの掲示板にあげて質問することも厳に止めてください。
CC BY-SA 4.0
2020-06-06T13:09:30.093
67367
2020-06-06T13:54:39.390
2020-06-06T13:54:39.390
40518
40518
66354
answer