トップ回答者
TableAdapterでトランザクション処理(BeginTransactionを使う方法)がうまくいきません・・・

質問
-
皆様お世話になります。
トランザクション処理なしで実行すると、正常に全行削除されてから新規追加されているのですが、
下記のようにトランザクション処理を追加するとデータベース上で何も変化が起きなくなってしまいました・・
どうか不備な点をご指摘いただければと思います。
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つの方法のうちの一つを別スレッドとしてあげさせていただきました。
回答
-
パッと見た感じですが、tr.Begin();が必要ないように思います。これを実行することにより、もう一つ別の入れ子になったトランザクションが開始されます。そのトランザクションでデータの更新を行っていますが、元々のトランザクションがコミットされていないため、全てロールバックされているように思います。
#tr.Commit()をもう一度行ってみると、上記のことが確認できるのではないかと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク spna 2011年12月19日 10:31
-
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
-
先のレスで、> という訳で、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
-
aviator__さんが言われるように手動で設定しても良いのですが、おそらく更新もできないのではないかと思います。TableAdapterを作成する際に、DeleteCommand、UpdateCommandを自働的に作成するオプションがありますが、そこにチェックが入っていないのではないでしょうか? 入っていても作成されない場合はテーブルに主キーが無いのだと思います。テーブルに主キーが無い場合は、手動でDeleteCommandやUpdateCommandを設定するSQL文自体が書けない可能性があります。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク spna 2011年12月19日 9:06
すべての返信
-
パッと見た感じですが、tr.Begin();が必要ないように思います。これを実行することにより、もう一つ別の入れ子になったトランザクションが開始されます。そのトランザクションでデータの更新を行っていますが、元々のトランザクションがコミットされていないため、全てロールバックされているように思います。
#tr.Commit()をもう一度行ってみると、上記のことが確認できるのではないかと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク spna 2011年12月19日 10:31
-
Visual Studio はどのバージョンをお使いでしょうか?
多分、TableAdapterでトランザクションを制御する機能を提供しているのはVisual Studo 2010からだったような気がします。
この場合でも、開始したトランザクションを、TableAdapterのプロパティに明示的にセットする必要があったと思いますが。
※これは、Transactionは、コマンドにも設定する必要があるためです。2010より前のバージョンの場合は、TransactionをTableAdapterに設定する機能自体がないため、強引なことをしないとそもそも実現できません。
--訂正
2008からできるようですね、失礼しました。- 編集済み なちゃ 2011年12月16日 15:13
-
try~catch内でエラーが発生しているのにRollbackして終わってるから更新されてないんじゃないかなぁ
テーブルアダプタのTransactionプロパティにBeginTransaction()で開いたTransactionを入れておきます。
#Begin()で入れ子にしたなら、その入れ子のTransactionを入れておきます。入れ子のトランザクションでは最上位のCommitで下位も暗黙でCommitされたような
逆に最上位をCommitしないとRollbackされるはず。
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!) -
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
-
先のレスで、> という訳で、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
-
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(); } } } }
-
お世話になります。
「一旦全行削除」の前に移動してみましたが、結果は変わりませんでした・・・
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(); } } } }
-
aviator__さんが言われるように手動で設定しても良いのですが、おそらく更新もできないのではないかと思います。TableAdapterを作成する際に、DeleteCommand、UpdateCommandを自働的に作成するオプションがありますが、そこにチェックが入っていないのではないでしょうか? 入っていても作成されない場合はテーブルに主キーが無いのだと思います。テーブルに主キーが無い場合は、手動でDeleteCommandやUpdateCommandを設定するSQL文自体が書けない可能性があります。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 回答としてマーク spna 2011年12月19日 9:06
-
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(); } } } }
-
> ※やはり下記がないと、削除部分が成功しませんでした・・・
>
> //さらにこれを追加して解決した。
> this._adp.Fill(_tbl);最初の話では、
> トランザクション処理なしで実行すると、正常に全行削除されてから新
> 規追加されているのですが、下記のようにトランザクション処理を追加
> するとデータベース上で何も変化が起きなくなってしまいました・・ということだったので、アップされたコード以外のどこかで Fill してい
たはずですが、実は「正常に全行削除され」というのは思い違いだったの
でしょうか。それとも「トランザクション処理を追加」した際に Fill を
削除したのでしょうか。何にせよ、DataSet/DataTable を Fill しなければ、TableAdapter.Update
で既存のレコードは削除できませんね。- 編集済み SurferOnWww 2011年12月22日 13:09 誤字訂正
-
ご返信ありがとうございます。
ご指摘の点についてですが、
今回の処理では
1.全権削除
2.次にデータの追加
ですので、
アプリケーション作成後第一回目の実行では
1.全権削除
⇒データは空なので削除件数は0件
2.次にデータの追加
⇒データが追加される。
これで一連の動作が終わるため、次の実行時には
2.で追加されたデータが存在したままになっています。
ですので2回目以降の実行時には常にデータが存在しており、
「正常に全行削除され」にあたるのはこのレコードでした。
いろいろ分析してくださってありがとうございました。
- 編集済み spna 2011年12月31日 13:16