none
TableAdapterでトランザクション処理(BeginTransactionを使う方法)がうまくいきません・・・ RRS feed

  • 質問

  • 皆様お世話になります。

    トランザクション処理なしで実行すると、正常に全行削除されてから新規追加されているのですが、

    下記のようにトランザクション処理を追加するとデータベース上で何も変化が起きなくなってしまいました・・

    どうか不備な点をご指摘いただければと思います。

    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.テーブル1TableAdapter _adp;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
             
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._tbl = new DB1DataSet.テーブル1DataTable();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                _adp.Connection.Open();
                System.Data.OleDb.OleDbTransaction tr = _adp.Connection.BeginTransaction();
    
                try
                {
                    //トランザクション開始
                    tr.Begin();
    
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
                    //テーブル更新
                    _adp.Update(_tbl);
    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
                    //テーブル更新
                    _adp.Update(_tbl);
    
                    //コミット
                    tr.Commit();
                }
                catch
                {
                    tr.Rollback();
                }
                finally
                {
                    _adp.Connection.Close();
                }
            }
        }
    }
    
    
    
    ※ http://social.msdn.microsoft.com/Forums/ja-JP/vsgeneralja/thread/cd7ceb28-0287-4ec9-97db-c7d730c5e9eb

    にてご教授いただきました2つの方法のうちの一つを別スレッドとしてあげさせていただきました。

    2011年12月16日 6:27

回答

  • パッと見た感じですが、tr.Begin();が必要ないように思います。これを実行することにより、もう一つ別の入れ子になったトランザクションが開始されます。そのトランザクションでデータの更新を行っていますが、元々のトランザクションがコミットされていないため、全てロールバックされているように思います。

    #tr.Commit()をもう一度行ってみると、上記のことが確認できるのではないかと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月19日 10:31
    2011年12月16日 7:52
    モデレータ
  • r.Begin(); はたぶん不要でしょう。それをコメントアウトし
    ても解決しないのは Command オブジェクトに Transaction が
    設定されてないからだと思います。

    Visual Studio 2008 から TableAdapter に Transaction プロ
    パティが追加されたようです。OleDb の場合以下のようになっ
    ています。

    private global::System.Data.OleDb.OleDbTransaction _transaction;
    
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    internal global::System.Data.OleDb.OleDbTransaction Transaction {
      get {
        return this._transaction;
      }
      set {
        this._transaction = value;
        for (int i = 0; (i < this.CommandCollection.Length); i = (i + 1)) {
          this.CommandCollection[i].Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.DeleteCommand != null))) {
          this.Adapter.DeleteCommand.Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.InsertCommand != null))) {
          this.Adapter.InsertCommand.Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.UpdateCommand != null))) {
          this.Adapter.UpdateCommand.Transaction = this._transaction;
        }
      }
    }
    
    

    という訳で、Visual Studio 2008 以降を使っているなら、以下
    の 1 行を Upadte メソッドの前に追加すれば解決するのではな
    いでしょうか。

    _adp.Transaction = tr;

    Visual Studio 2005 以前の場合、Transaction プロパティは自動
    生成されたコードには定義されませんが、TableAdapter は partial
    クラスとして定義されているので、自動生成されたファイルとは別に、
    上記のコードを参考に、自力で Transaction プロパティを定義して
    使うのがいいと思います。

    その他、_adp.Update(_tbl); を 2 回実行する必要はなく、最後に
    1 回実行すればいいはずです。

    また、例外は catch したままにしないで throw した方がいいと思い
    ます。下記ページ参照。

    .NETの例外処理 Part.2
    http://blogs.msdn.com/b/nakama/archive/2009/01/02/net-part-2.aspx

     

    • 回答としてマーク spna 2011年12月19日 10:22
    2011年12月16日 13:11

  • 先のレスで、

    > という訳で、Visual Studio 2008 以降を使っているなら、以下
    > の 1 行を Upadte メソッドの前に追加すれば解決するのではな
    > いでしょうか。

    と書きましたが、Visual Studio 2008 以降を使っているなら、そ
    もそも自力でトランザクションをかける必要はなかったです。

    自動生成される TableAdapterManager クラスの UpdateAll メソッ
    ドのコードをよく見てみれば、その中でトランザクションがかかる
    ようになってました。

    どうも、Visual Studio 2008 から TableAdapter に Transaction
    プロパティが追加された理由は、TableAdapterManager.UpdateAll
    メソッドでトランザクションをかけるためのようです。

    という訳で、Visual Studio 2008 以降なら TableAdapterManager
    の UpdateAll メソッドを使うのが正解だと思います。

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

    チュートリアル : データベースへのデータの保存 (単一テーブル)
    http://msdn.microsoft.com/ja-jp/library/0f92s97z.aspx

    なお、上記のチュートリアルの「アプリケーションに更新ロジックを
    追加するには」のセクションに自力でコードを追加するように書かれ
    ていますが、自動的に以下のようなコードが生成されるはずです。

    private void customersBindingNavigatorSaveItem_Click(object sender, EventArgs e)
    {
      this.Validate();
      this.customersBindingSource.EndEdit();
      this.tableAdapterManager.UpdateAll(this.northwindDataSet);
    }
    

    • 回答としてマーク spna 2011年12月19日 10:22
    2011年12月18日 7:27
  • 「DB1DataSetTableAdapters」 のクラスのデザイナに 「テーブル1TableAdapter」 オブジェクトがいるかと思うんですが、

    そこにDeleteCommandプロパティっていませんか?

     

    軽く想像で話してます・・・

    • 回答としてマーク spna 2011年12月19日 10:32
    2011年12月19日 7:41
  • aviator__さんが言われるように手動で設定しても良いのですが、おそらく更新もできないのではないかと思います。TableAdapterを作成する際に、DeleteCommand、UpdateCommandを自働的に作成するオプションがありますが、そこにチェックが入っていないのではないでしょうか? 入っていても作成されない場合はテーブルに主キーが無いのだと思います。テーブルに主キーが無い場合は、手動でDeleteCommandやUpdateCommandを設定するSQL文自体が書けない可能性があります。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月19日 9:06
    2011年12月19日 8:11
    モデレータ

すべての返信

  • パッと見た感じですが、tr.Begin();が必要ないように思います。これを実行することにより、もう一つ別の入れ子になったトランザクションが開始されます。そのトランザクションでデータの更新を行っていますが、元々のトランザクションがコミットされていないため、全てロールバックされているように思います。

    #tr.Commit()をもう一度行ってみると、上記のことが確認できるのではないかと思います。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月19日 10:31
    2011年12月16日 7:52
    モデレータ
  • いつもありがとうございます。

    早速

    // tr.Begin();

    のようにコメントアウトしてトライしてみましたが、

    結果は変わりませんでした・・・

     

    2011年12月16日 8:30
  • Visual Studio はどのバージョンをお使いでしょうか?

    多分、TableAdapterでトランザクションを制御する機能を提供しているのはVisual Studo 2010からだったような気がします。
    この場合でも、開始したトランザクションを、TableAdapterのプロパティに明示的にセットする必要があったと思いますが。
    ※これは、Transactionは、コマンドにも設定する必要があるためです。

    2010より前のバージョンの場合は、TransactionをTableAdapterに設定する機能自体がないため、強引なことをしないとそもそも実現できません。

    --訂正
    2008からできるようですね、失礼しました。

    • 編集済み なちゃ 2011年12月16日 15:13
    2011年12月16日 8:56
  • try~catch内でエラーが発生しているのにRollbackして終わってるから更新されてないんじゃないかなぁ

    テーブルアダプタのTransactionプロパティにBeginTransaction()で開いたTransactionを入れておきます。
    #Begin()で入れ子にしたなら、その入れ子のTransactionを入れておきます。

    入れ子のトランザクションでは最上位のCommitで下位も暗黙でCommitされたような
    逆に最上位をCommitしないとRollbackされるはず。


     


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
    2011年12月16日 9:06
  • r.Begin(); はたぶん不要でしょう。それをコメントアウトし
    ても解決しないのは Command オブジェクトに Transaction が
    設定されてないからだと思います。

    Visual Studio 2008 から TableAdapter に Transaction プロ
    パティが追加されたようです。OleDb の場合以下のようになっ
    ています。

    private global::System.Data.OleDb.OleDbTransaction _transaction;
    
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    internal global::System.Data.OleDb.OleDbTransaction Transaction {
      get {
        return this._transaction;
      }
      set {
        this._transaction = value;
        for (int i = 0; (i < this.CommandCollection.Length); i = (i + 1)) {
          this.CommandCollection[i].Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.DeleteCommand != null))) {
          this.Adapter.DeleteCommand.Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.InsertCommand != null))) {
          this.Adapter.InsertCommand.Transaction = this._transaction;
        }
        if (((this.Adapter != null) 
                  && (this.Adapter.UpdateCommand != null))) {
          this.Adapter.UpdateCommand.Transaction = this._transaction;
        }
      }
    }
    
    

    という訳で、Visual Studio 2008 以降を使っているなら、以下
    の 1 行を Upadte メソッドの前に追加すれば解決するのではな
    いでしょうか。

    _adp.Transaction = tr;

    Visual Studio 2005 以前の場合、Transaction プロパティは自動
    生成されたコードには定義されませんが、TableAdapter は partial
    クラスとして定義されているので、自動生成されたファイルとは別に、
    上記のコードを参考に、自力で Transaction プロパティを定義して
    使うのがいいと思います。

    その他、_adp.Update(_tbl); を 2 回実行する必要はなく、最後に
    1 回実行すればいいはずです。

    また、例外は catch したままにしないで throw した方がいいと思い
    ます。下記ページ参照。

    .NETの例外処理 Part.2
    http://blogs.msdn.com/b/nakama/archive/2009/01/02/net-part-2.aspx

     

    • 回答としてマーク spna 2011年12月19日 10:22
    2011年12月16日 13:11

  • 先のレスで、

    > という訳で、Visual Studio 2008 以降を使っているなら、以下
    > の 1 行を Upadte メソッドの前に追加すれば解決するのではな
    > いでしょうか。

    と書きましたが、Visual Studio 2008 以降を使っているなら、そ
    もそも自力でトランザクションをかける必要はなかったです。

    自動生成される TableAdapterManager クラスの UpdateAll メソッ
    ドのコードをよく見てみれば、その中でトランザクションがかかる
    ようになってました。

    どうも、Visual Studio 2008 から TableAdapter に Transaction
    プロパティが追加された理由は、TableAdapterManager.UpdateAll
    メソッドでトランザクションをかけるためのようです。

    という訳で、Visual Studio 2008 以降なら TableAdapterManager
    の UpdateAll メソッドを使うのが正解だと思います。

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

    チュートリアル : データベースへのデータの保存 (単一テーブル)
    http://msdn.microsoft.com/ja-jp/library/0f92s97z.aspx

    なお、上記のチュートリアルの「アプリケーションに更新ロジックを
    追加するには」のセクションに自力でコードを追加するように書かれ
    ていますが、自動的に以下のようなコードが生成されるはずです。

    private void customersBindingNavigatorSaveItem_Click(object sender, EventArgs e)
    {
      this.Validate();
      this.customersBindingSource.EndEdit();
      this.tableAdapterManager.UpdateAll(this.northwindDataSet);
    }
    

    • 回答としてマーク spna 2011年12月19日 10:22
    2011年12月18日 7:27
  • SufferOnWww様

    ご教授ありがとうございます。

    UpdateAll()を使う方法は、完全なソースが書ききれておりませんので、

    書きあがり次第、別スレッドにしてUPしたいと考えております(どう書いてもうまく行かない状態ですので、一番ましなソースを書いてUPします・・・)

    さて、それとは別に一つ前にご提案くださった、

    ***********************************************

    という訳で、Visual Studio 2008 以降を使っているなら、以下
    の 1 行を Upadte メソッドの前に追加すれば解決するのではな
    いでしょうか。

    _adp.Transaction = tr;

    ***********************************************

    のほうで少し進捗がございましたので、書き進めたい思います。

    ※申し送れてすみません・・ Visual Studio 2008 +  .netFramework2.0(配布先の事情)で作成しております。

    下記のようなソースで実験しましたところ、

    button1をクリックすると、削除されずに追加だけされました。

    デバッグを終了せずに、もう一度、button1をクリックすると

    throw;の行のところで

    {"更新には、削除された行を含む DataRow コレクションが渡されたとき、有効な DeleteCommand が必要です。"}

    とエラーが出ました・・・

    ご指摘を賜りたいと思います・・・

    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.テーブル1TableAdapter _adp;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
             
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._tbl = new DB1DataSet.テーブル1DataTable();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                _adp.Connection.Open();
                System.Data.OleDb.OleDbTransaction tr = _adp.Connection.BeginTransaction();
    
                try
                {
                    //トランザクション開始
                    //tr.Begin();
    
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
                    //テーブル更新
                    _adp.Transaction = tr;
                    _adp.Update(_tbl);
    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
                    //テーブル更新
                    _adp.Transaction = tr;
                    _adp.Update(_tbl);
    
                    //コミット
                    tr.Commit();
                }
                catch
                {
                    tr.Rollback();
                    throw;
                }
                finally
                {
                    _adp.Connection.Close();
                }
            }
        }
    }
    
    

     

    2011年12月19日 3:00
  • エラーについては、テーブル1TableAdapter のDeleteCommandが設定されてないのではないですか?

    合わせて、「_adp.Transaction = tr;」の記載についてですが、「一旦全行削除」の前に移動した方が良いかと・・・

     

    見当違いでしたらすいません、先に謝っておきます。

    2011年12月19日 4:02
  • お世話になります。

    「一旦全行削除」の前に移動してみましたが、結果は変わりませんでした・・・

    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.テーブル1TableAdapter _adp;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
    
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._tbl = new DB1DataSet.テーブル1DataTable();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                _adp.Connection.Open();
                System.Data.OleDb.OleDbTransaction tr = _adp.Connection.BeginTransaction();
    
                try
                {
                    //トランザクション開始
                    //tr.Begin();
                    _adp.Transaction = tr;
    
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
                    //テーブル更新                
                    _adp.Update(_tbl);
    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
                    //テーブル更新
                    _adp.Update(_tbl);
    
                    //コミット
                    tr.Commit();
                }
                catch
                {
                    tr.Rollback();
                    throw;
                }
                finally
                {
                    _adp.Connection.Close();
                }
            }
        }
    }
    
    

     

     

     

    2011年12月19日 5:45
  • いや・・・

    エラーについては、テーブル1TableAdapter のDeleteCommandが設定されてないのではないですか?

    2011年12月19日 6:16
  • お世話になっております。

    大変申しわけございませんでした。

    DeleteCommandを設定するにはどのようにすればよいのでしょうか・・・

     

     

    2011年12月19日 6:59
  • 「DB1DataSetTableAdapters」 のクラスのデザイナに 「テーブル1TableAdapter」 オブジェクトがいるかと思うんですが、

    そこにDeleteCommandプロパティっていませんか?

     

    軽く想像で話してます・・・

    • 回答としてマーク spna 2011年12月19日 10:32
    2011年12月19日 7:41
  • aviator__さんが言われるように手動で設定しても良いのですが、おそらく更新もできないのではないかと思います。TableAdapterを作成する際に、DeleteCommand、UpdateCommandを自働的に作成するオプションがありますが、そこにチェックが入っていないのではないでしょうか? 入っていても作成されない場合はテーブルに主キーが無いのだと思います。テーブルに主キーが無い場合は、手動でDeleteCommandやUpdateCommandを設定するSQL文自体が書けない可能性があります。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク spna 2011年12月19日 9:06
    2011年12月19日 8:11
    モデレータ
  • trapemiya様

    ありがとうございます。

    ご指摘の通り、何度もテストするたびにレコードを消さなければならないことを避けるために

    主キーを外してテストプロジェクトを組んでおりました。

    主キーを設定し、かつ下記のようにFillで最初にDataTableを埋めるようにするとうまくいきました。

    ありがとうございました。(解決)

    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.テーブル1TableAdapter _adp;
            private DB1DataSet.テーブル1DataTable _tbl;
    
            public Form1()
            {
                InitializeComponent();
    
                this._adp = new DB1DataSetTableAdapters.テーブル1TableAdapter();
                this._tbl = new DB1DataSet.テーブル1DataTable();            
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                _adp.Connection.Open();
                System.Data.OleDb.OleDbTransaction tr = _adp.Connection.BeginTransaction();
                _adp.Transaction = tr;
                //さらにこれを追加して解決した。
                this._adp.Fill(_tbl);
    
                try
                {
                    //一旦全行削除
                    foreach (DB1DataSet.テーブル1Row r in _tbl.Rows)
                    {
                        r.Delete();
                    }
                    //テーブル更新                
                    _adp.Update(_tbl);
    
                    
                    //その後、新規に追加
                    DB1DataSet.テーブル1Row row = _tbl.Newテーブル1Row();
                    row.a = "AAA";
                    row.b = "BBB";
                    _tbl.Addテーブル1Row(row);
                    //テーブル更新
                    _adp.Update(_tbl);
                    
                    //コミット
                    tr.Commit();
                }
                catch
                {
                    tr.Rollback();
                    throw;
                }
                finally
                {
                    _adp.Connection.Close();
                }
            }
        }
    }
    
    

     

    2011年12月19日 10:21
  • 解決とのことですが・・・

    > 主キーを設定し、かつ下記のようにFillで最初にDataTableを埋める
    > ようにするとうまくいきました。

    次のステップに進む前に、何が本当の問題だったかを明確にすること
    をお勧めします。

    おそらく前者のみが今回の問題の原因で、後者は関係ないと思います。

    何より一番の問題は、その影響を考えずに、テーブルのスキーマを変
    更した(主キーをなくした)ことだと思います。

     

     

    2011年12月19日 13:00
  • SurferOnWww様

    ご指摘の点、了解いたしました。

    このたびは種々ご教示いただき、ありがとうございました。


    ※やはり下記がないと、削除部分が成功しませんでした・・・

                //さらにこれを追加して解決した。
                this._adp.Fill(_tbl);

    • 編集済み spna 2011年12月21日 11:52
    2011年12月20日 1:11
  • > ※やはり下記がないと、削除部分が成功しませんでした・・・
    >
    >            //さらにこれを追加して解決した。
    >            this._adp.Fill(_tbl);

    最初の話では、

    > トランザクション処理なしで実行すると、正常に全行削除されてから新
    > 規追加されているのですが、下記のようにトランザクション処理を追加
    > するとデータベース上で何も変化が起きなくなってしまいました・・

    ということだったので、アップされたコード以外のどこかで Fill してい
    たはずですが、実は「正常に全行削除され」というのは思い違いだったの
    でしょうか。それとも「トランザクション処理を追加」した際に Fill を
    削除したのでしょうか。

    何にせよ、DataSet/DataTable を Fill しなければ、TableAdapter.Update
    で既存のレコードは削除できませんね。

     


    • 編集済み SurferOnWww 2011年12月22日 13:09 誤字訂正
    2011年12月22日 13:05
  • SurferOnWww

    ご返信ありがとうございます。

    ご指摘の点についてですが、

    今回の処理では

    1.全権削除

    2.次にデータの追加

    ですので、

    アプリケーション作成後第一回目の実行では

    1.全権削除

    ⇒データは空なので削除件数は0件

    2.次にデータの追加

    ⇒データが追加される。

     

    これで一連の動作が終わるため、次の実行時には

    2.で追加されたデータが存在したままになっています。

    ですので2回目以降の実行時には常にデータが存在しており、

    「正常に全行削除され」にあたるのはこのレコードでした。

    いろいろ分析してくださってありがとうございました。


    • 編集済み spna 2011年12月31日 13:16
    2011年12月31日 13:15