AppNewでエラーが発生してしまう

[戻る]
田中さん 2007/02/14(水) 23:02:33
こんにちわ。Win2003Sv+VisualC.Net+SDKにて開発しています。

Accessのデータを扱うプログラムを作っています。
それでAddNewを使い、データ追加をしようとしているのですが
AddNew()をするとエラーがでてしまいます。以下のようなコードを書いているのですが
問題がありそうな所はありませんでしょうか?



_ConnectionPtr pConnection = NULL;
_RecordsetPtr pRecordset = NULL;
RsTapeList rsTapeList;
IADORecordBinding   *picRs = NULL;

HRESULT hr=S_OK;

//OLEDBの初期化
CoInitialize(NULL);

bstr_t strCnn("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\testlist.mdb;");

try{
//Connection作成
TESTHR(pConnection.CreateInstance(__uuidof(Connection)));
hr = pConnection->Open(strCnn,"","",NULL);

//レコードセット作成
TESTHR(pRecordset.CreateInstance(__uuidof(Recordset)));
pRecordset->Open("TestList",_variant_t((IDispatch *) pConnection, 
true),adOpenKeyset,adLockOptimistic,adCmdTable);

//レコードセットをバインドする
TESTHR(pRecordset->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*)&picRs));
        TESTHR(picRs->BindToRecordset(&rsTestList));   

//空レコードを追加
TESTHR(picRs->AddNew(&rsTestList));

//レコードオブジェクトに値を入れる
rsTestList.m_TestNo=20;
strcpy(rsTestList.m_TestName,"20");

//更新実行
TESTHR(picRs->Update(&rsTestList));
}

//**** コードは以上です。 ****

<ComError>:
Code=800a0c93
Code meaning=Unknown error 0x800A0C93

エラーは上記の物が出ます。出る場所はAddNew()をした所です。

Blue 2007/02/14(水) 23:35:32
> RsTapeList 
ってなに?

空レコードを追加するだけならば、引数なしのAddNewでよいのでは?
その後、

pRecordset->Fields->GetItem("TestNo")->Value = 20L;

のように値を設定するのではないでしょうか?

参考
http://msdn.microsoft.com/library/ja/default.asp?
url=/library/ja/jpado260/htm/mdmthupdatexvc.asp
の
> UpdateX2 Function
のところの AddNew が出てくるあたり。

ITO 2007/02/16(金) 10:29:51
><ComError>:
>Code=800a0c93
>Code meaning=Unknown error 0x800A0C93
「Unknown error」だとすると、accessからのエラーってことはないですか?
ADOは分りませんが、accessなら日本語でエラーメッセージが返ってくるはずです。
いつでもデバッグモードで、プロバイダーからのエラーを
見れるようにしておくと楽です。

>空レコードを追加するだけならば、引数なしのAddNewでよいのでは?
出来ましたっけ?
確か、数字は0、文字はスペースでも入っていないと駄目だったと思いました。
ADOは可能なんでしょうか。

Blue 2007/02/16(金) 10:51:49
>出来ましたっけ?
http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/ado270/htm/mdmthaddnew.asp
をみると、

>FieldList 
>Optional. 
となっています。
タイプライブラリを#importしたとき、省略時にはデフォルトでVT_EMPTYのVARIANT型の
値(vtMissing)が指定されるはずだったような。
(実際、第二引数のValuesは省略されていますね。)

しかも、指定するのは「文字列型の配列」(VT_BSTRのSAFEARRAY)ですので、C++から指定
するとなると意外と面倒ですし。




というか、できないなら先に載せたURLのMSのADOのコードが間違っているってことにな
りますよね?



ちなみに、SQL文を使えば、AddNewの出番はほぼないかと思うんだけれども。

Blue 2007/02/16(金) 11:53:01
日本語だと
http://msdn.microsoft.com/library/ja/default.asp?
url=/library/ja/jpado260/htm/mdmthaddnew.asp
ですね。

解説の後半部分に引数省略時の動作が書かれていますね。

んで、そこからたどれる「Visual C++ の例」のところですが、
確かに
>CEmployeeRs emprs;    //C++ class object
>TESTHR(picRs->AddNew(&emprs));
ってのがありますね。

どうも
>IADORecordBinding   *picRs = NULL;  //Interface Pointer declared.(VC++ 
Extensions)
> TESTHR(pRstEmployees->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*)
&picRs));
>TESTHR(picRs->BindToRecordset(&emprs));
で、C++のクラスとフィールドを結び付けれるようです。
http://msdn.microsoft.com/library/ja/default.asp?
url=/library/ja/jpado260/htm/mdmscusingadovcextensions.asp

で、元に戻るのですが、
>RsTapeList>Visual C++ Extensions クラス CADORecordBinding から派生したクラスで宣言します
となっているのか不明なので、提示されたコードだけでNGかどうかは判定できない
んじゃないかなと。
http://msdn.microsoft.com/library/ja/default.asp?
url=/library/ja/jpado260/htm/mdmscexampleadowithextensions.asp
になっているのかとか。



それと、もともとナニを参考にしてコードを書いているかを明確にしたほうがよいで
す。実際ここまでたどりつくのにMSDNを検索したわけで。

Blue 2007/02/16(金) 14:05:33
いろいろ試してみて原因っぽいのがわかった。

どうもAddNewは何かすでにテーブルにレコードが1行以上ないとだめらしい。
対策はどうするんだろ?

それと、

>//空レコードを追加
>TESTHR(picRs->AddNew(&rsTestList));
これは、空のレコードを入れたことにはなりません。
RecordsetをOpenしたときに指定したテーブルの先頭行の値が格納されてしまうので
空のレコードではなく、先頭のレコードと同じデータを追加したことになります。

Blue 2007/02/16(金) 15:16:27
> どうもAddNewは何かすでにテーブルにレコードが1行以上ないとだめらしい。
は、間違っていたっぽい。

RsTapeListのBEGIN_ADO_BINDING〜END_ADO_BINDINGを正しく記述しないと
うまくいかないことがあるようです。

ですので結局のところRsTapeListとC:\testlist.mdbのTestListのテーブルデザインを見
てみないと原因が探れないようです。

Blue 2007/02/16(金) 16:04:36
一応試してみたコードです。
(コンストラクタを入れないと正常に動かないっぽい?)

#include <windows.h>
#include <icrsint.h>
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
    no_namespace rename("EOF", "EndOfFile")

_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));

inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); }

class RsTapeList : public CADORecordBinding
{
BEGIN_ADO_BINDING(RsTapeList)
    ADO_NUMERIC_ENTRY(1, adInteger, m_TestNo, 10, 0, m_TestNoStatus, TRUE)
    ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_TestName,
        sizeof(m_TestName), m_TestNameStatus, TRUE)
END_ADO_BINDING()
public:
    RsTapeList()
    : m_TestNo(0), m_TestNoStatus(adFldOK), m_TestNameStatus(adFldOK)
    { memset(m_TestName, 0x00, sizeof(m_TestName)); }

    int  m_TestNo;       // 1番目のフィールド : TestNo 数値型
    char m_TestName[21]; // 2番目のフィールド : TestName テキスト型(サイズ:20)
    unsigned long m_TestNoStatus;
    unsigned long m_TestNameStatus;
};

HRESULT Test()
{
    HRESULT hr = S_OK;

    _ConnectionPtr pCon = NULL;
    _RecordsetPtr  pRes = NULL;
    RsTapeList rsTestList;

    _bstr_t strCnn(L"Provider=Microsoft.Jet.OLEDB.4.0;"
                   L"Data Source=C:\\testlist.mdb;");

    try
    {
        TESTHR(pCon.CreateInstance(__uuidof(Connection)));
        TESTHR(pCon->Open(strCnn, L"", L"", adConnectUnspecified));

        TESTHR(pRes.CreateInstance(__uuidof(Recordset)));
        pRes->Open(L"TestList", _variant_t((IDispatch*)pCon, true), 
                   adOpenKeyset, adLockOptimistic, adCmdTable);

        IADORecordBindingPtr picRs(pRes);
        TESTHR(picRs->BindToRecordset(&rsTestList));

        TESTHR(picRs->AddNew(&rsTestList));

        rsTestList.m_TestNo = 20;
        strcpy(rsTestList.m_TestName, "20");

        TESTHR(picRs->Update(&rsTestList));

        pRes->Close();
        pCon->Close();
    }
    catch (_com_error& e)
    {
        hr = e.Error();
    }
    if (pRes) pRes.Release();
    if (pCon) pCon.Release();

    return hr;
}

int main()
{
    if (SUCCEEDED(::CoInitialize(NULL)))
    {
        Test();
        ::CoUninitialize();
    }

    return 0;
}

ITO 2007/02/16(金) 16:51:57
>タイプライブラリを#importしたとき、省略時にはデフォルトでVT_EMPTYのVARIANT型の
>値(vtMissing)が指定されるはずだったような。
もともとはVB用?

> しかも、指定するのは「文字列型の配列」(VT_BSTRのSAFEARRAY)ですので、
> C++から指定するとなると意外と面倒ですし。
なるほど、それは、面倒ですね。

>ちなみに、SQL文を使えば、AddNewの出番はほぼないかと思うんだけれども。
同意見
Accessなら日本語でエラーメッセージが出るし、楽だと思います。
手間は手間ですが.....

>どうも
>>IADORecordBinding   *picRs = NULL;  //Interface Pointer declared.(VC++ 
>Extensions)
>> TESTHR(pRstEmployees->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*)
>&picRs));
>>TESTHR(picRs->BindToRecordset(&emprs));
>で、C++のクラスとフィールドを結び付けれるようです。
本来
SQL文発行
 ↓
BINDCOL
 ↓
 実行
だと思いますので会っていると思います。


>それと、もともとナニを参考にしてコードを書いているかを明確にしたほうがよいで
>す。実際ここまでたどりつくのにMSDNを検索したわけで。
そうですね、ADOの資料ってもともと少ないです。

>どうもAddNewは何かすでにテーブルにレコードが1行以上ないとだめらしい。
>対策はどうするんだろ?
ADOはCOM経由にせよODBCのインターフェースなので一度SQL文を発行
しないといけないんじゃないかと思います。

田中さん 2007/02/20(火) 21:17:25
Blueさん ITOさん返信ありがとうございます。

お二人の投稿から色々と四苦八苦した所、
BEGIN_ADO_BINDING(RsTapeList)
    ADO_NUMERIC_ENTRY(1, adInteger, m_TestNo, 10, 0, m_TestNoStatus, TRUE)
    ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_TestName,
        sizeof(m_TestName), m_TestNameStatus, TRUE)
END_ADO_BINDING()
のレコードセットのクラス書き方が悪い事が分かりました。

ADO_NUMERIC_ENTRY(1, adInteger, m_TestNo, 10, 0, m_TestNoStatus, TRUE)
と書くべき所を

ADO_NUMERIC_ENTRY(1, adInteger, m_TestNo, sizeof(m_TapeNo), m_TestNoStatus, 
FALSE)

としていました。
ADO_NUMERIC_ENTRY(No,型,バッファ、桁数、小数点桁数、ステータス、変更可否)
と書き直しました。

何も値が無い場合はエラーになるようで、これはAddNewの仕様だと思うのでひとまず置い
ておきます。何も値が無い場合はAppendを使えばいいという事ですよね?

とりあえず値が入力できるようになったのですが、もう一つ問題が分かりました。

登録するフィールドに主KEYが設定されていると
「<ProviderError> 
 number[80040e21]:インデックス、主キー、またはリレーションシップで値が
重複しているので、テーブルを変更できませんでした。重複する値のある
フィールドの値を変更するか、インデックスを削除してください。
または重複する値を使用できるように再定義してください。
」

というエラーが出て困っています。
C++でのADOは難しい〜〜。

Blue 2007/02/20(火) 22:25:52
ですから、

>TESTHR(picRs->AddNew(&rsTestList));
>rsTestList.m_TestNo = 20;
>strcpy(rsTestList.m_TestName, "20");
AddNewしてから値を設定するのは無理です。
値を設定してからAddNewしてください。

理由は指摘済み
>>//空レコードを追加
>>TESTHR(picRs->AddNew(&rsTestList));
>これは、空のレコードを入れたことにはなりません。
>RecordsetをOpenしたときに指定したテーブルの先頭行の値が格納されてしまうので
>空のレコードではなく、先頭のレコードと同じデータを追加したことになります。

Blue 2007/02/21(水) 00:41:57
追記

>C++でのADOは難しい〜〜。
CADORecordBindingを使うような記述をしているから難しいと思うのでしょう。
VBからだったらそんなことしないで、INSERT文を発行するはずです。

で、ITOさんからも私からも言っていますが、
わからないようなのを使うよりシンプルでサンプル豊富なSQL文を使うべきでしょう。
そのほうが融通が利くと思いますし。

一応過去ログでADOからSQLをExcuteしているのがあります。
Accessのデーターベースへのレコード挿入
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200609/06090053.txt


パラメタが特殊で文字列に出来ない場合は、CommandオブジェクトからCreateParameter
メソッドをつかって、、、とVBと同じような手順を踏んでいけばそんなに難しくはない
でしょう。


それと、
>どうもAddNewは何かすでにテーブルにレコードが1行以上ないとだめらしい。
ですが、過去ログ
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200412/04120025.txt
ってのがありましたね。解答はついていないけど。
おそらく、レコードがないとき、コンストラクタできちんと初期化処理を入れておかな
いと、CADORecordBindingの派生クラスの各メンバが、(RecordSetをOpenしても先頭のレ
コードの値が入らないため)不定な値になってしまい、INSERTできないのかなと思ってい
ます。

田中さん 2007/02/21(水) 00:48:32
Blueさんの御指摘で
>AddNewしてから値を設定するのは無理です。
>値を設定してからAddNewしてください。
という所ですが、AddNewでフィールドに何も値が無い場合は値が入力出来ないのでは
ないでしょうか?色々と試していますが、どうも思ったような結果になってくれません。

例えば以下のようなコードに変更したのですが、フィールドに値が無い場合はエラーが出
てしまいます。

//レコードオブジェクトに値を入れる
rsTestList.m_TestNo=20;
strcpy(rsTestList.m_TestName,"20番を入力");
//AddNewモード
TESTHR(picRs->AddNew(&rsTestList));        
rsTestList.m_TestNo=21;
strcpy(rsTestList.m_TestName,"21番を入力");
//更新実行
TESTHR(picRs->Update(&rsTestList));

・・・データは21番のデータが入ります。
レコードのクラスファイルの定義がオカシイ可能性がありますので断言は出来ない状態で
すが。

先ほどの主キーを設定した場合のエラーはレコードのクラスファイルが原因でした。

Blue 2007/02/21(水) 01:05:36
> ・・・データは21番のデータが入ります。
当たり前ですけど?

20番を入れて21番を入れてってことをやるなら

//レコードオブジェクトに値を入れる
rsTestList.m_TestNo=20;
strcpy(rsTestList.m_TestName,"20番を入力");
//AddNewモード
TESTHR(picRs->AddNew(&rsTestList));        
//更新実行
TESTHR(picRs->Update(&rsTestList));

rsTestList.m_TestNo=21;
strcpy(rsTestList.m_TestName,"21番を入力");
//AddNewモード
TESTHR(picRs->AddNew(&rsTestList));        
//更新実行
TESTHR(picRs->Update(&rsTestList));

てな感じになるのではないでしょうか?

>フィールドに値が無い場合はエラーが出てしまいます。
はよくわからないのですが。

ITO 2007/02/21(水) 09:33:42
>>フィールドに値が無い場合はエラーが出てしまいます。
>はよくわからないのですが。
SQL文による実行と同じだと思います。
INSERT文の構文で、フィールドに値がないとエラーになるはずです。
> 先ほどの主キーを設定した場合のエラーはレコードのクラスファイルが原因でした。
主キーは変更できません。
これは、accessでの処理もエラーになると思います。

田中さん 2007/02/21(水) 16:25:34
[[解決]]
BlueさんITOさん、ありがとうございました。

ある程度意図する事が出来るようになりましたので
ひとまず解決とさせて頂きます。
SQL文を使ったINSERTはある程度やった事がありますので
そちらでもコードを試してみます。
どうもでした。

ITO 2007/02/21(水) 16:32:52
>レコードのクラスファイルの定義がオカシイ可能性がありますので断言は出来ない
>状態ですが。
>先ほどの主キーを設定した場合のエラーはレコードのクラスファイルが原因でした。
今までの流れを見ると
    レコードのクラスファイルの定義 =SQL文でいうフィールドの部分
となると思います。
レコードのクラスファイルの定義とAccessのデータの構造をすべて一致させる
 必要があると思います。
その点、SQL文で作ればその場でエラーを返してくれるので手間な分デバッグは
 楽だと思います。
AccessでもSQL文をチェックできます。

古いスレッドには追加発言できません。