TableAdapter親クラスの変更

TableAdapterクラスには親クラスを差し替える機能があるのは知っていたのですが、使い道は全く思いつきませんでした。
(データセットデザイナで TableAdapterにあるBaseClassプロパティを変更することで親を変更できる)

プログラミング MS ADO .NET2.0 (マイクロソフト公式解説書)

プログラミング MS ADO .NET2.0 (マイクロソフト公式解説書)

に書いてありましたが、著者も「どうやって使うかわからない」だそうです。


でも有効利用しているサンプルを見つけました。
http://www.codeproject.com/useritems/typed_dataset_transaction.asp


TableAdapterでマニュアルトランザクションを使う簡単な方法はpartialクラスを使いTransactionを公開します。
しかし、TableAdapterごとに書くのは面倒・・・

ということで、親クラスでトランザクションの制御をするクラスを作ったようです。


そのソース

// ----------------------------------------------------------------------------
//
// TransactionSupport
//
// Original author: Mike Pagel
//
// After ideas given in
//     http://www.codeproject.com/useritems/typed_dataset_transaction.asp
//     http://entwickler-forum.de/showpost.php?p=2032&postcount=2
//
// ----------------------------------------------------------------------------

using System.Data.SqlClient;
using System.Data;
using System.Reflection;

namespace BizWiz
{
    /// <summary>
    /// Transaction support for generated table adapters.
    /// </summary>
    /// <remarks>
    /// This class adds transaction support to table adapters. It is used by changing the base
    /// class of a table adapter from Component to this class. The implementation of this class
    /// then accesses the derived table adapter's properties through reflection.
    /// </remarks>
    public abstract class TransactionSupport : System.ComponentModel.Component
    {
        // --------------------------------------------------------------------
        #region Reflective access to table adapter properties
        // --------------------------------------------------------------------
        private SqlConnection Connection
        {
            // Access to propertis as public and non-public as generated table-adapter
            // scope seems to be different for different installations:
            // http://www.codeproject.com/useritems/transactionta.asp?msg=2225021#xx2225021xx
            get
            {
                return (SqlConnection)GetType().GetProperty( "Connection", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( this, null );
            }
            set
            {
                GetType().GetProperty( "Connection", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( this, value, null );
            }
        }

        // --------------------------------------------------------------------
        private SqlDataAdapter Adapter
        {
            get
            {
                return (SqlDataAdapter)GetType().GetProperty( "Adapter", BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( this, null );
            }
        }

        // --------------------------------------------------------------------
        private SqlCommand[] CommandCollection
        {
            get
            {
                return (SqlCommand[])GetType().GetProperty( "CommandCollection", BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( this, null );
            }
        }

        #endregion
        // --------------------------------------------------------------------
        #region Properties
        // --------------------------------------------------------------------
        /// <summary>
        /// Transaction of this table adapter.
        /// </summary>
        /// <remarks>
        /// This property is used to share a transaction and its associated connection
        /// across multiple table adapters. The typical pattern is shown in the sample
        /// code below.
        /// </remarks>
        /// <example>
        /// XTableAdapter xta = new XTableAdapter();
        /// YTableAdapter yta = new YTableAdapter();
        /// 
        /// xta.BeginTransation();
        /// yta.Transation = xta.Transaction;
        /// try
        /// {
        ///     // perform xta and yta modifications here.
        ///     xta.CommitTransaction();
        /// }
        /// catch( Exception )
        /// {
        ///     xta.RollbackTransaction();
        /// }
        /// </example>
        public SqlTransaction Transaction
        {
            get
            {
                return transaction;
            }
            set
            {                
                // attach transaction to all commands of this adapter:
                if( CommandCollection != null )
                {
                    foreach( SqlCommand command in CommandCollection )
                    {
                        command.Transaction = value;
                    }
                }
                if( Adapter.InsertCommand != null )
                {
                    Adapter.InsertCommand.Transaction = value;
                }
                if( Adapter.UpdateCommand != null )
                {
                    Adapter.UpdateCommand.Transaction = value;
                }
                if( Adapter.DeleteCommand != null )
                {
                    Adapter.DeleteCommand.Transaction = value;
                }
         
                // also set connection of this adapter accordingly:
                if( value != null )
                {
                    Connection = value.Connection;
                }
                else
                {
                    // only clear connection if it was attached to
                    // transaction before:
                    if( transaction != null ) Connection = null;
                }

                transaction = value;
            }
        }

        #endregion
        // --------------------------------------------------------------------
        #region Operations
        // --------------------------------------------------------------------
        public void BeginTransaction()
        {
            // Open the connection, if needed
            if( Connection.State != ConnectionState.Open )
                Connection.Open();

            // Create the transaction and assign it to the Transaction property
            Transaction = Connection.BeginTransaction();
        }

        // --------------------------------------------------------------------
        public void CommitTransaction()
        {
            // Commit the transaction
            Transaction.Commit();

            // Close the connection
            Connection.Close();
        }

        // --------------------------------------------------------------------
        public void RollbackTransaction()
        {
            // Rollback the transaction
            Transaction.Rollback();

            // Close the connection
            Connection.Close();
        }

        #endregion
        // --------------------------------------------------------------------
        #region Fields
        // --------------------------------------------------------------------
        /// <summary>
        /// Fields supporting properties.
        /// </summary>
        private SqlTransaction transaction;

        #endregion
        // --------------------------------------------------------------------

    }    
}

なるほど、リフレクションなら実行時にコネクション取れるわけですね。
なるほど。


使い方

using (TransactionScope transaction = new TransactionScope())
{
    adapter1.Update(table1);
    adapter2.Update(table2);

    transaction.Complete();
}


でも思ったのですがリフレクション使うんだったら、以下のようなstaticメソッド作って

public static void SetTransaction(Component ta, SqlTransaction tx)
{
    // コネクション取得
    Type typTA = ta.GetType();
    PropertyInfo propCon = typTA.GetProperty("Connection", BindingFlags.NonPublic | BindingFlags.Instance);

    if (propCon == null)
    {
        throw new System.ArgumentException();
    }

    // テーブルアダプターの取得
    PropertyInfo propAdapter = typTA.GetProperty("Adapter", BindingFlags.NonPublic | BindingFlags.Instance);
    if (propAdapter == null)
    {
        throw new System.ArgumentException();
    }

    Object objAdapter = propAdapter.GetValue(ta, null);
    if (objAdapter is SqlDataAdapter)
    {
        // コネクションをテーブルアダプターにセットする
        propCon.SetValue(ta, tx.Connection, null);
        SqlDataAdapter adapter = (SqlDataAdapter)objAdapter;

        // 各コマンドにトランザクションをセットする
        SqlCommand[] CommandCollection = (SqlCommand[])typTA.GetProperty(
            "CommandCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ta, null);

        if (CommandCollection != null)
        {
            foreach (SqlCommand command in CommandCollection)
            {
                command.Transaction = tx;
            }
        }

        if (adapter.UpdateCommand != null)
        {
            adapter.UpdateCommand.Transaction = tx;
        }

        if (adapter.InsertCommand != null)
        {
            adapter.InsertCommand.Transaction = tx;
        }

        if (adapter.DeleteCommand != null)
        {
            adapter.DeleteCommand.Transaction = tx;
        }

        if (adapter.SelectCommand != null)
        {
            adapter.SelectCommand.Transaction = tx;
        }

    }
}

こんな感じで使う方が簡単じゃないかと。
(結局TableAdapterの親クラスをいちいち変更するのが面倒だと思う)

using (SqlConnection con = new SqlConnection("〜〜〜"))
{
    con.Open();
    SqlTransaction tx = con.BeginTransaction();
    DBUtil.SetTransaction(ta1, tx);
    DBUtil.SetTransaction(ta2, tx);

    ta1.Update();
    ta2.Update();

    tx.Commit();
}