DOBON.NETプログラミング道掲示板

■34789 / 親記事)  DataAdapterで2回目のUpDateが出来ない
  
□投稿者/ Wan 一般人(18回)-(2021/06/26(Sat) 12:22:42)
  • アイコン環境/言語:[Windows10 VisualStudio2019 VB.net WindowsForm] 
    分類:[.NET] 

    次のようなコードで、DataAdapterを使いまわして二つのTabelを更新しようとしています。既存のデータは一旦全て削除して、ListデータをDataTabelに変換(拡張メソッドを作成しています:ToDataTable)してMargeして更新しようとしています。
    1回目(Dt説明)は上手くいくのですが、2回目のUpDateの時に、'SourceColumn '説明' の DataColumn  '説明' が見つかりません。'と怒られます。
    上手く使いまわす方法を教えてください。
    宜しくお願い致します。
    
    
         Dim MyDir As String = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase
            Dim Dir既存DB As String = MyDir.Substring(0, 1) & ":\DataBaseTestFolder/"
            Dim ConStr As String = "Provider = Microsoft.ACE.OLEDB.12.0;" &
                               "Data Source=" & Dir既存DB &
                               "株データベース.accdb"
            Dim Dt説明 As New DataTable
            Dim Dt銘柄 As New DataTable
    
            Dim Sql説明Select As String = "Select * From 説明Dt"
            Dim Sql銘柄Select As String = "Select * From 銘柄Dt"
            Dim conn As OleDb.OleDbConnection = New OleDb.OleDbConnection(ConStr)
            Dim cmd As OleDb.OleDbCommand = New OleDb.OleDbCommand(Sql説明Select, conn)
            Dim Da As New OleDb.OleDbDataAdapter
            Dim ComBuilder As New OleDb.OleDbCommandBuilder(Da)
    
            Da.SelectCommand = cmd
            Da.Fill(Dt説明)
            Da.InsertCommand = ComBuilder.GetInsertCommand
            Da.DeleteCommand = ComBuilder.GetDeleteCommand
            For Each LopDtRow As DataRow In Dt説明.Rows
                LopDtRow.Delete()
            Next
            Dt説明.Merge(List説明.ToDataTable)
            Da.Update(Dt説明)
    
            conn = New OleDb.OleDbConnection(ConStr)
            cmd = New OleDb.OleDbCommand(Sql銘柄Select, conn)
            Da.SelectCommand = cmd
            Da.Fill(Dt銘柄)
            Da.InsertCommand = ComBuilder.GetInsertCommand
            Da.DeleteCommand = ComBuilder.GetDeleteCommand
            For Each LopDtRow As DataRow In Dt銘柄.Rows
                LopDtRow.Delete()
            Next
            Dt銘柄.Merge(List銘柄.ToDataTable)
            Da.Update(Dt銘柄)

マルチポストを報告
違反を報告
引用返信 削除キー/
■34792 / ResNo.1)  Re[1]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ 魔界の仮面弁士 大御所(1357回)-(2021/06/26(Sat) 16:05:10)
  • アイコンNo34789に返信(Wanさんの記事)
    > conn = New OleDb.OleDbConnection(ConStr)

    1 つ目の接続を Close / Dispose することなく、
    新たに 2 つ目の接続を開いているのは何故ですか?

    接続先が同じなら 2 つ目の接続を張る必要は無いですし、
    開きなおすのが目的なら、最初の接続を閉じてから繋ぐべきですよね。


    > 既存のデータは一旦全て削除して、
    トランザクションを明示した上で、DELETE クエリを投げるのでは駄目なのでしょうか。

    WHERE の無い SELECT で全データを取得し、その内容を読むことも無く
    ループ処理で取得したデータに削除マークを付けていってから、
    一件一件 DELETE クエリを投げつけるという現状の実装は、無駄が多すぎるかと。


    > 2回目のUpDateの時に、…と怒られます。
    ComBuilder.GetInsertCommand().CommandText を確認すると分かるかと思いますが、
    1 回目の `説明` なクエリ情報が残っているからです。

    今回の場合、`説明` を終えた時点で、Da や ComBuilder を使いまわしているので、
     Da Is ComBuilder.DataAdapter
     Da.SelectCommand Is ComBuilder.DataAdapter.SelectCommand
     Da.InsertCommand Is ComBuilder.DataAdapter.InsertCommand
     Da.DeleteCommand Is ComBuilder.DataAdapter.DeleteCommand
     Da.UpdateCommand Is ComBuilder.DataAdapter.UpdateCommand
    が、いずれかも True な状態であることは分かるかと思いますが、
    それとは別に、CommandBuilder はコマンド生成時の「列情報」を内部的に保持しており、
    同一 Adapter に対しては、以前作成した時のコマンドをキャッシュする設計になっています。
    この動作は OleDb に限らず、他の ADO.NET プロバイダーでも同様です。


    > 上手く使いまわす方法を教えてください。
    cmd や conn は使いまわさずに毎回 New しなおしているのに、
    Da や ComBuilder だけ使いまわしている点に非対称性を感じますが、
    意図的にそのような実装にしているのでしょうか。

    アダプター類を再生成するかどうかは好みの問題とはいえ、
    今回のケースなら conn の方を使いまわすべきに思えました。


    それはさておき、今回の修正案としては…。

    案1) そもそも CommandBuilder を使いまわさず、再生成する。
     ※どうせ SelectCommand/InsertCommand/DeleteCommand/UpdateCommand 総入れ替えなので。

    案2) コマンドを作り直す前に、ComBuilder.RefreshSchema() を呼び出しておく。
     ※これは以前のコマンドを削除するメソッドです。

    案3) DataAdapter のみを再生する。すなわち、
     「Da.SelectCommand = cmd」の代わりに
     『ComBuilder.DataAdapter = New OleDb.OleDbDataAdapter(cmd)』とする。
     ※ DataAdapter プロパティを差し替えた場合、自動的に RefreshSchema が呼ばれる仕様です。
違反を報告
引用返信 削除キー/
■34793 / ResNo.2)  Re[2]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ Wan 一般人(20回)-(2021/06/26(Sat) 17:17:37)
  • アイコン
    早速のご指導有難う御座います。
    基本中の基本だと思いますが、私のために時間を割いて頂ければと・・
    
    >1 つ目の接続を Close / Dispose することなく、
    >新たに 2 つ目の接続を開いているのは何故ですか?
    DataAtapterは、Fill、UpDateをすると自動でOpen、Closeを行うので必要ないと思っています。
    
    >トランザクションを明示した上で、DELETE クエリを投げるのでは駄目なのでしょうか。
    Da.DeleteCommand = New OledDb.OleCommand(“Dalete From 説明Dt”)
    Da.Fill(Dt説明)
    という記述で、Deleteクエリを投げたつもり(コードがここまで進んだ段階で、Accessの出たーを見ると消えていない)ですが、上手くいきませんでした。
    
    >WHERE の無い SELECT で全データを取得し、その内容を読むことも無く
    >ループ処理で取得したデータに削除マークを付けていってから、
    >一件一件 DELETE クエリを投げつけるという現状の実装は、無駄が多すぎるかと。
    上記の方法で、上手くいかなかったので、説明Dt.Clearとしたのですが、上手くいかず、ググるとループで削除マークを付ける必要があるとのことなので、読み込んだものを一切触らず、削除後、Margeしています。ただ、この次のステップで、キーが重複しているものを削除するつもりなので、一旦読み込むことは、アリ?(クエリで処理すべき?)かな?って考えています。
    
    >ComBuilder.GetInsertCommand().CommandText を確認すると分かるかと思いますが、
    >1 回目の `説明` なクエリ情報が残っているからです。
    これは、想像していましたが、対処方法がわからないので質問したことになります。
    
    >今回の場合、`説明` を終えた時点で、Da や ComBuilder を使いまわしているので、
    > Da Is ComBuilder.DataAdapter
    > Da.SelectCommand Is ComBuilder.DataAdapter.SelectCommand
    > Da.InsertCommand Is ComBuilder.DataAdapter.InsertCommand
    > Da.DeleteCommand Is ComBuilder.DataAdapter.DeleteCommand
    > Da.UpdateCommand Is ComBuilder.DataAdapter.UpdateCommand
    >が、いずれかも True な状態であることは分かるかと思いますが、
    Trueの状態をいう概念を私が理解できていないのはさておき、ComBuilderは、Daと関連付けて生成しているので、DaのSelect文のみ更新すれば、GetInsertCommandメソッドを呼べば、更新後のSelect文に対応するInsert文が取得できると考えていました。UpData文に関しては、生成していないし、使用(Rowの内容を触らない)しないので問題外と考えていました。Insert文とDelete文は、Getメソッドで上書きしているので問題ないと考えていました。
    
    >同一 Adapter に対しては、以前作成した時のコマンドをキャッシュする設計になってい>ます。
    >この動作は OleDb に限らず、他の ADO.NET プロバイダーでも同様です。
    これは、エラーの内容から想像できました。
    
    >cmd や conn は使いまわさずに毎回 New しなおしているのに、
    >Da や ComBuilder だけ使いまわしている点に非対称性を感じますが、
    >意図的にそのような実装にしているのでしょうか。
    全てをNewするのは、無駄なので、出来る限り使いまわそうとしたのですが、上手くいかないので、これは使いまわせないのかな?って考え少しずつNewを増やした結果です。根本的に理解していない私です。connをNewしているのは、ComをNewしても上手くいかなかったので、connもNewしないとだめなのかな?って思い追加。
    
    >アダプター類を再生成するかどうかは好みの問題とはいえ、
    >今回のケースなら conn の方を使いまわすべきに思えました。
    勉強させて頂きます。アダプタ類をNewした方が、余計な処理が省けるということですね?
    接続先が一緒なので、Connは使いまわす。
    
    >案1) そもそも CommandBuilder を使いまわさず、再生成する。
    > ※どうせ SelectCommand/InsertCommand/DeleteCommand/UpdateCommand 総入れ替えなので。
    そのように致します。
    
    >案2) コマンドを作り直す前に、ComBuilder.RefreshSchema() を呼び出しておく。
    > ※これは以前のコマンドを削除するメソッドです。
    これも試してみますが、案1の方が、直観的なような気がしますので、近い将来、自分でコードを見た時に、何してんだ?とならないように・・・
    
    >案3) DataAdapter のみを再生する。すなわち、
    > 「Da.SelectCommand = cmd」の代わりに
    > 『ComBuilder.DataAdapter = New OleDb.OleDbDataAdapter(cmd)』とする。
    > ※ DataAdapter プロパティを差し替えた場合、自動的に RefreshSchema が呼ばれる
    >仕様です。
    なるほど・・です。が、同じく、案1でコードは残し、コメントで、案2、案3を列記しておきます。将来スキルが、上がった時(が来るかどうかは別として)に役に立つように・・
    
    丁寧なご指導有難う御座いました。
    大変助かっています。
    

違反を報告
引用返信 削除キー/
■34794 / ResNo.3)  Re[3]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ Wan 一般人(21回)-(2021/06/26(Sat) 17:19:06)
  • アイコン
    解決しました

解決み!
違反を報告
引用返信 削除キー/
■34796 / ResNo.4)  Re[3]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ 魔界の仮面弁士 大御所(1358回)-(2021/06/26(Sat) 20:49:34)
  • アイコンUpdate を UpDate や UpData と書いているのがちょっと気になる…。


    No34793に返信(Wanさんの記事)
    >> 1 つ目の接続を Close / Dispose することなく、
    >> 新たに 2 つ目の接続を開いているのは何故ですか?
    > DataAtapterは、Fill、UpDateをすると自動でOpen、Closeを行うので必要ないと思っています。

    その自動開閉によって、現状、Open/Close が 4 回繰り返されることになりますよね。

    Da.Fill(Dt説明)  'conn1.Open → SELECT * FROM `説明` → conn1.Close
    Da.Update(Dt説明) 'conn1.Open → DELETE & UPDATE `説明` → conn1.Close
    Da.Fill(Dt銘柄)  'conn2.Open → SELECT * FROM `銘柄` → conn2.Close
    Da.Update(Dt銘柄) 'conn2.Open → DELETE & UPDATE `銘柄` → conn2.Close


    DataAdaper を一回しか使わないケースなら自動開閉任せでも良いのですが、複数回呼び出す場合は
    DataAdapter の操作前に Open し、処理が終わった後に Close させた方が望ましいです。
    たとえば下記の実験例だと、100 回の DataAdapter.Fill で、50 倍の速度差になっています。
    http://okwakatta.net/code/ado12.html

    今回のケースだとこんなイメージかな。
     Using conn As New OleDb.OleDbConnection(ConStr)
      conn.Open()
       :
      Da.Fill(Dt説明)
       :
      Da.Update(Dt説明)
       :
      Da.Fill(Dt銘柄)
       :
      Da.Update(Dt銘柄)
       :
      conn.Close()
     End Using


    実際には、OleDbTransaction クラスも併用して
    トランザクション処理を組み入れる必要がありますね。
    (OleDbConnection の接続先が *.mdb や *.accdb の場合は、特に重要です)


    >> トランザクションを明示した上で、DELETE クエリを投げるのでは駄目なのでしょうか。
    > Da.DeleteCommand = New OledDb.OleCommand(“Dalete From 説明Dt”)
    (OleCommand というのはスペルミスとして…)

    > Da.Fill(Dt説明)
    DataAdapter の『Fill メソッド』は、レコードを DataTable に取り込む目的にしか使えないのです。

    INSERT / DELETE / UPDATE 等を呼ぶ場合は、OleDbCommand の
    『ExecuteNonQuery メソッド』を使ってみてください。
    このメソッドの戻り値は、処理件数を示す Integer 値です。

    OleDbCommand にはこのほか、SELECT を呼ぶ場合に使われる
    『ExecuteScalar メソッド』… 1 行 1 列な単一データを取得する際に使う
    『ExecuteReader メソッド』… レコードCSV出力するなど、行単位の処理で済む場合
    などが用意されています。併せて習得しておきましょう。

    データを SELECT するには、DataAdapter の『Fill メソッド』でも良いのですが、
    たとえば大量のレコードを持つテーブルの内容を CSV ファイルに保存するような処理の場合、
    Da.Fill だと全レコードをすべて DataTable に取り込んでから CSV 化するため、メモリ負荷が大きくなります。
    それを cmd.ExecuteReader で得た OleDbDataReader をループ処理する方法に変更すれば、
    現在行のための 1 行分のメモリしか消費せずに済むため、パフォーマンスが向上します。


    > 読み込んだものを一切触らず、削除後、Margeしています。
    × Marge
    ○ Merge


    > ググるとループで削除マークを付ける必要があるとのことなので、
    ToDataTable の実装がどのようになっているか分かりませんが、
    生成された ToDataTable で得られる各行(DataRow) の RowState プロパティの
    状態によって、Merge 結果が変わってくるかと思います。


    > キーが重複しているものを削除するつもりなので、
    ん? データベースがは全件削除するのですよね。
    キー重複というのは ToDataTable のことでしょうか。

    元が List というなら、DataTable 側でどうこうするより、
    List 側を LINQ した方が手っ取り早いかもしれません。

    DataTable 側で処理するなら、PrimaryKey をセットしておけば、
    親 DataSet の EnforceConstraints プロパティによる検査で
    DataTable.GetErrors メソッドからエラー行を取得することも一応できます。


    >> Da Is ComBuilder.DataAdapter
    >> が、いずれかも True な状態であることは分かるかと思いますが、
    > Trueの状態をいう概念を私が理解できていないのはさておき、
    Is の両辺が同一のオブジェクト インスタンスであるということです。
    「If Da Is ComBuilder.DataAdapter Then」が真である状態。

    たとえば、SQL 文を下記のように書き換えた場合、
     Da.SelectCommand.CommandText = SQL
    これによって
     If ComBuilder.DataAdapter.SelectCommand.CommandText = SQL Then
    も True になります。DataAdapter が同一なので。


    > 全てをNewするのは、無駄なので、出来る限り使いまわそうとしたのですが、
    それは物によります。Connection や Command などはデータベースと直接やりとりする部分なので、
    同一内容であれば使いまわす方が良いでしょう。異なる内容ならその都度生成しても問題ありません。

    DataSet/DataTable などは、特定のデータベースと直接通信するものでないので、
    繰り返し回数がそれほど多くないなら、毎回 New しようと、使いまわそうと大差無いです。

    しかし、繰り返し処理の回数が多いプログラムや、長い日数動き続けるプログラムの場合は別。

    たとえば、table.Rows.Clear() してから、.Rows.Add あるいは DataAdapter.Fill する処理を
    繰り返していくと、Rows.Add の処理時間が目に見えて遅くなってしまう事例があるのですが、
    新しい DataTable を New しなおしてから行追加すると、この問題を回避できます。

    …といっても下記のような問題もあったりするので、どちらにせよ長時間の稼働には向かないのですが。
    http://bbs.wankuma.com/index.cgi?mode=al2&namber=81781&KLOG=140


    > アダプタ類をNewした方が、余計な処理が省けるということですね?
    そうですね。とはいえ好みの問題などもあるので、使いやすい方法を選んでみてください。
解決み!
違反を報告
引用返信 削除キー/
■34798 / ResNo.5)  Re[4]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ Wan 一般人(24回)-(2021/06/28(Mon) 17:35:18)
  • アイコン誤字、脱字は大変申し訳ございません。
    インテリセンスに頼りすぎとボキャブラリーが余りにも少なく反省です。

    DataAdapterで、Open/Closeの件は、知識としては、知っていましたが、余り重要視していませんでした。そんなに、違いが出てくるのですね?

    トランザクションは、価値を全く理解していません。なぜ?Accessならなおさらなのか?言われても、価値が・・・

    ExecuteNonQuery メソッドは、Netからデータを取得して、単純にDataBaseに追加するだけの時は、常時接続で使っていました。が、今回は、既存のデータベースを全削除して、と思ったので、DataAdapterを使おうと考え、既存のテーブルを全削除するのに、常時接続との混在はスマートではないので、DataAdapterで、全削除しようと思い。Loopにたどり着いたのが、実情です。結果。ぐちゃぐちゃなった。

    全データをメモリに読み込んでいては、データベースを使っている意味が無いですね?反省。

    LINQも、仮面さんにここで教えて貰ったので、ちょこちょこかじっている状態です。根本的に理解しないで、ググったコードをちょこっと改造して使ってやれって気持ちが、だめなんですね?

    反省しています。




解決み!
違反を報告
引用返信 削除キー/
■34800 / ResNo.6)  Re[5]: DataAdapterで2回目のUpDateが出来ない
□投稿者/ 魔界の仮面弁士 大御所(1359回)-(2021/06/28(Mon) 19:09:08)
  • アイコンNo34798に返信(Wanさんの記事)
    > トランザクションは、価値を全く理解していません。なぜ?Accessならなおさらなのか?言われても、価値が・・・

    SQL Server や Oracle などのデータベースとは異なり、
    Access の *.accdb / *.mdb は「ファイル共有型データベース」です。

    そのため、ファイルのロックはサーバー上で画一的に行われるのではなく、
    それぞれのアプリケーション上で個別に実施されます。
    そのため、排他制御において幾許かの制限があるのです。

    その一つが、『更新結果が即座に反映されない』というものです。

    既定で「遅延(非同期)書込み」が行われるようになっていることから、
    マルチユーザー環境においては、最大で 0.5 秒の間、追加したレコードが、
    他のユーザーの接続からは認識されないという問題が生じます。

    しかし、トランザクションを明示的に切れば「同期書き込み」となり、
    コミットした時点で、直ちにデータを反映させることができます。
    詳細は下記をご覧ください。
    2001年当時の古い資料であり、.NET 向けに書かれた物でもありませんが、
    非同期書き込みという点については、ほぼそのまま当てはまります。

    [Jet Engineのキャッシュとその制御]
    http://www.canalian.com/workshop/access/JetCache.html

    この仕様があるがゆえに、JET Provider や ACE Provider においては、
    たとえデータ更新の SQL を一つ投げるだけであったとしても、
    明示的にコミットするのが無難である、ということです。
解決み!
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -