none
TableAdapterManagerのUpdateAll()を使ったトランザクションがうまくいきません。 RRS feed

  • 質問

  • お世話になります。

    http://social.msdn.microsoft.com/Forums/ja/vsgeneralja/thread/c780ecd0-95a1-48ab-a37a-4c2e204c38c9

    のほうで、「UpdateAll()を使った方法については別スレッドにUPします」ということで

    新規スレッドとしてあげさせていただきました。

    改めて、環境はVisualStudio2008 + .netFramework2.0 を使用しております。

    TableAdapterManagerのUpdateAll()を使ってトランザクション処理を実現したいのですが、

    実行してもDBに何の変化も起きません。

    不備な点をどうかご指摘いただけないでしょうか。

    よろしくお願いいたします。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private DB1DataSetTableAdapters.TableAdapterManager _adpm;
            private DB1DataSet _dst;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
             
                this._adpm = new DB1DataSetTableAdapters.TableAdapterManager();
                this._dst = new DB1DataSet();
                this._tbl = new DB1DataSet.テーブル1DataTable();
                
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                try
                {
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
    
                    //コミット
                    _adpm.UpdateAll(_dst);
                
                }
                catch
                {
                    throw;
                }
                
            }
        }
    }
    
    



     

    2011年12月19日 6:28

回答

  • TableAdapterManager、DataSet、DataTable、それぞれのインスタンスの関係をきちんと理解する必要があります。この三者のインスタンス間に関連を持たせなければ正しく動作しません。関連を持たせなければそれぞれのインスタンスが他と無関係に動作することになります。また、TableAdapterManger内部でTableAdapterを利用するので、TableAdapterのインスタンスも必要です。以下のようにしてみて下さい。

                this._adpm = new DB1DataSetTableAdapters.TableAdapterManager();
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._dst = new DB1DataSet();
                
        //      this._tbl = new DB1DataSet.テーブル1DataTable();
                this._tbl =  _dst.テーブル1;
                
                _adpm.テーブル1TableAdapter = _adp;
    

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月21日 9:49
    2011年12月20日 8:43
    モデレータ
  • > 質問のように、フォームに連結をしないシナリオでUpdateAll()を
    > 使ってトランザクションをかけるにはどのようにすればよろしいで
    > しょうか・・・

    「フォームに連結をしない」という意味が不明ですが、Windows フォ
    ームアプリケーションですよね。

    それなら、答えは自動生成された Form2.cs と Form2.Designer.cs の
    コードの中にすべて含まれています。だから、先のレスで

    > 使い方は、一度以下のチュートリアルでドラッグ&ドロップでアプ
    > リケーションを作って、自動生成されたコードを見るとよいと思い
    > ます。
     
    と書いたのですが、見つからなかったでしょうか?


    spna さんが書いたコードでは以下の点が気になります。(1) ~ (3)
    は terapemiya さんの指摘と同じです。

    (1) TableAdapter が見あたらない。

    (2) TableAdapterManager に TableAdapter が関係付けられてない。

    (3) _dst の中の DataTable と _tbl は別モノ。_tbl の中身を操作し
    て、UpdateAll(_dst) としても DB は更新されない。

    (4) try, catch には意味がない。

    ただし、アップされた部分以外にも多々コードがあるはずで、以前の
    ようにその部分に問題があるかもしれません。上記 (1) ~ (3) に対
    処してもうまくいかない場合は、自動生成されたコードを見て、どう
    すればよいか考えてください。

    どうしてもうまくいかなければ、From2 の既存の部分はとりあえずそ
    のままにしておき、From2 の空いている場所に Button を新たにツー
    ルボックスからドラッグ&ドロップして追加して、Click イベントの
    ハンドラで「this.dB1DataSet.テーブル1」に対して行削除/追加を行
    った後、

    this.tableAdapterManager.UpdateAll(this.dB1DataSet);

    を実行してやればうまくいくと思います。

     


    • 編集済み SurferOnWww 2011年12月20日 14:19 誤記訂正
    • 回答としてマーク spna 2011年12月21日 11:52
    2011年12月20日 12:57

すべての返信

  • 先のスレッドのレスで、

    > 使い方は、一度以下のチュートリアルでドラッグ&ドロップでアプ
    > リケーションを作って、自動生成されたコードを見るとよいと思い
    > ます。
     
    > チュートリアル : データベースへのデータの保存 (単一テーブル)
    > http://msdn.microsoft.com/ja-jp/library/0f92s97z.aspx

    とアドバイスしましたが、それはやってないのですか? それをやっ
    てもらわないと話が通じないと思います。

     

    2011年12月19日 15:14
  • SurferOnWww様

    いつもお世話になっております。

    下記のように実際に試してみており、

    フォームに連結してUpdateAll()する分には成功しております。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form2 : Form
        {
            public Form2()
            {
                InitializeComponent();
            }
    
            private void テーブル1BindingNavigatorSaveItem_Click(object sender, EventArgs e)
            {
                this.Validate();
                this.テーブル1BindingSource.EndEdit();
                this.tableAdapterManager.UpdateAll(this.dB1DataSet);
    
            }
    
            private void Form2_Load(object sender, EventArgs e)
            {
                // TODO: このコード行はデータを 'dB1DataSet.テーブル1' テーブルに読み込みます。必要に応じて移動、または削除をしてください。
                this.テーブル1TableAdapter.Fill(this.dB1DataSet.テーブル1);
    
            }
        }
    }
    
    

     

    質問のように、フォームに連結をしないシナリオでUpdateAll()を使ってトランザクションをかけるには

    どのようにすればよろしいでしょうか・・・

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private DB1DataSetTableAdapters.TableAdapterManager _adpm;
            private DB1DataSet _dst;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
             
                this._adpm = new DB1DataSetTableAdapters.TableAdapterManager();
                this._dst = new DB1DataSet();
                this._tbl = new DB1DataSet.テーブル1DataTable();
                
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                try
                {
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
    
                    //コミット
                    _adpm.UpdateAll(_dst);
                
                }
                catch
                {
                    throw;
                }
                
            }
        }
    }
    
    
    

    のように書いてみておりますが、DBのほうに更新されずに困っております。

    ご教示賜れば光栄です。

    よろしくお願いいたします。


    • 編集済み spna 2011年12月20日 1:08
    2011年12月20日 1:07
  • TableAdapterManager、DataSet、DataTable、それぞれのインスタンスの関係をきちんと理解する必要があります。この三者のインスタンス間に関連を持たせなければ正しく動作しません。関連を持たせなければそれぞれのインスタンスが他と無関係に動作することになります。また、TableAdapterManger内部でTableAdapterを利用するので、TableAdapterのインスタンスも必要です。以下のようにしてみて下さい。

                this._adpm = new DB1DataSetTableAdapters.TableAdapterManager();
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._dst = new DB1DataSet();
                
        //      this._tbl = new DB1DataSet.テーブル1DataTable();
                this._tbl =  _dst.テーブル1;
                
                _adpm.テーブル1TableAdapter = _adp;
    

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月21日 9:49
    2011年12月20日 8:43
    モデレータ
  • > 質問のように、フォームに連結をしないシナリオでUpdateAll()を
    > 使ってトランザクションをかけるにはどのようにすればよろしいで
    > しょうか・・・

    「フォームに連結をしない」という意味が不明ですが、Windows フォ
    ームアプリケーションですよね。

    それなら、答えは自動生成された Form2.cs と Form2.Designer.cs の
    コードの中にすべて含まれています。だから、先のレスで

    > 使い方は、一度以下のチュートリアルでドラッグ&ドロップでアプ
    > リケーションを作って、自動生成されたコードを見るとよいと思い
    > ます。
     
    と書いたのですが、見つからなかったでしょうか?


    spna さんが書いたコードでは以下の点が気になります。(1) ~ (3)
    は terapemiya さんの指摘と同じです。

    (1) TableAdapter が見あたらない。

    (2) TableAdapterManager に TableAdapter が関係付けられてない。

    (3) _dst の中の DataTable と _tbl は別モノ。_tbl の中身を操作し
    て、UpdateAll(_dst) としても DB は更新されない。

    (4) try, catch には意味がない。

    ただし、アップされた部分以外にも多々コードがあるはずで、以前の
    ようにその部分に問題があるかもしれません。上記 (1) ~ (3) に対
    処してもうまくいかない場合は、自動生成されたコードを見て、どう
    すればよいか考えてください。

    どうしてもうまくいかなければ、From2 の既存の部分はとりあえずそ
    のままにしておき、From2 の空いている場所に Button を新たにツー
    ルボックスからドラッグ&ドロップして追加して、Click イベントの
    ハンドラで「this.dB1DataSet.テーブル1」に対して行削除/追加を行
    った後、

    this.tableAdapterManager.UpdateAll(this.dB1DataSet);

    を実行してやればうまくいくと思います。

     


    • 編集済み SurferOnWww 2011年12月20日 14:19 誤記訂正
    • 回答としてマーク spna 2011年12月21日 11:52
    2011年12月20日 12:57
  • 皆様、手厚いご指導、本当にありがとうございます。

    trapemiya様

    おおせの通りでうまくいきました!

    あがとうございました。

    ただ一つ問題がありまして、

    複数回続けて実行すると「主キーがダブっている」

    と怒られましたので、一回目のDB更新の後、

    主キーだダブらないように手動でAAAをAAAZなどのようにAccessのレコードを変更した後、

    2回目を実行するとダブらずに更新されていたので、どうもInsertが先に走った後、Deleteされているようでした。

    そこでTableAdapterManagerのUpdateOrderをいじればいいやと思い、書こうとしますと、

    なんと

    .InsertUpdateDelete

    .UpdateInsertDelete

    しかありませんでした。

    Deleteを先に実行してくれるオプションはないのかと検索していますと

    http://stackoverflow.com/questions/6925140/how-can-i-change-tableadaptermanager-update-order-to-delete-insert-than-update

    に、ストアドプロシージャを書けとありました。

    ストアドを書く以外に方法があれば、是非ご助言いただければなお光栄ですが、

    本題は満たされましたので、ひとまず「解決」とさせていただきたいと思います。

    皆様本当にありがとうございました。

     

     

    2011年12月21日 9:49
  • ストアドを書く以外に方法があれば、是非ご助言いただければなお光栄ですが、

    TableAdapterManagerは階層更新を簡単に行えるようにするために、VS2008で追加されました。UpdateOrderはこの階層更新を制御するためのものであり、今回のような単一のテーブルでの更新順序の制御を目的とするものではありません。
    #個々のテーブルはUpdateOrderに従って更新されますので、結果的に単一のテーブルに対して指定しているのと同じになりますが・・・

    よって、削除を最初に行わなければならないケースは無い(はず)ため、UpdateOrderにそのようなオプションが用意されていないのでしょう。

    単一のテーブルを更新する場合は、主キーやユニークキーを操作しなければ、その更新順序で問題が発生することはありません。
    #なので、DataAdapterはデータテーブルの行を単に順番に反復処理しています。

    今回は主キーを操作しているために問題が発生しているわけです。
    主キーやユニークキーを操作する場合、その更新順序が問題になります。
    #ですから、お勧めは主キーはサロゲートキー(人工キー)にして、データベースで自動的に振らせることです。

    したがって、今回の場合はまず削除すれば良いので、UpdateAllの前に以下を実行すれば良いと思います。(コード未検証です)

    _adp.Update(_tbl.Select(null, null, DataViewRowState.Deleted))

    前述したようにTableAdapterManagerは階層更新のために生まれましたので、必然的に複数のTableAdapterを扱うことが前提となり、当然内部でトランザクションが切られます。今回はこのトランザクションを流用するためにTableAdapterManagerを使用しているわけですが、コード的にもわかりにくくなると思いますし、お勧めはTableAdapterを使い、素直に自分でトランザクションを切ることだと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年12月22日 6:23
    モデレータ
  • 方法の1つとして、主キーが同じレコードは Delete/Insert しないで Update すれば解決するのでは?
    2011年12月22日 6:31
  • 目的がわかりませんが、今回やりたいことは、Access の単一テー
    ブルの全レコードを削除してから、新たにレコードを作り直すとい
    う作業を、トランザクション処理を有効にして、プログラムで機械
    的に実行するということでしょうか?

    であれば、型付 DataSet+ TableAdapter + TableAdapterManager は
    目的に合わないと思います。

    接続型データアクセス用 ADO.NET ライブラリだけで、できればもっ
    とプリミティブにストアドプロシージャだけで、100% 自分でロジッ
    クをコーディングした方がいいのではないですか?

    どうしてもということなら、一つ前に戻って TableAdapter.Update
    で、トランザクションを含めて、処置することをお勧めします。


    > ストアドを書く以外に方法があれば、是非ご助言いただければ
    > なお光栄ですが、

    上記の理解が正しければ、今回のケースにはお勧めではありませんが、
    以下の手段が考えられます。

    TableAdapterManager クラスは partial として定義されているので、
    自動生成されたファイルとは別にクラスファイルを追加し、それに
    TableAdapterManager クラスを拡張する形で、Delete を先に行うメ
    ソッドを追加してそれを使うことで対応可能と思います。

    UpdateAll メソッドをコピペして、メソッド名前を適当なものに変え
    て、以下の部分を変更すれば、一応順序は変えられることは確認しま
    した。(ただし、他の部分で問題がないかは未検証です)

    // 
    //---- Perform updates -----------
    //
    if ((this.UpdateOrder == UpdateOrderOption.UpdateInsertDelete)) {
      result = (result + this.UpdateUpdatedRows(dataSet, allChangedRows, allAddedRows));
      result = (result + this.UpdateInsertedRows(dataSet, allAddedRows));
    }
    else {
      result = (result + this.UpdateInsertedRows(dataSet, allAddedRows));
      result = (result + this.UpdateUpdatedRows(dataSet, allChangedRows, allAddedRows));
    }
    result = (result + this.UpdateDeletedRows(dataSet, allChangedRows));
    


    #最初、型付 DataSet+ TableAdapter という話から始まっていたので、
    #ユーザー(人間)が PC の画面を見ながら、DataGridView などの UI
    #を通じて DataSet にアクセスして更新し、結果をボタンクリックで
    #DB に書き戻すという処置を想像していました。

     

    2011年12月22日 13:01