none
MVC の Model に相当する機能は .net framework にありますか? RRS feed

  • 質問

  • データ バインディングが必要な Form 間でデータを共有する場合、下記チュー
    トリアルを参考に、BindingSource を親 Form から子 Form に渡して同期を取っ
    ていました。


    - 方法 : BindingSource コンポーネントを使用してフォーム間でバインド データを共有する


    しかし、子 Form が多数になってきて、親から子に引数として BindingSource
    を渡すのが面倒になってきました。
    # 単に書くのが面倒になってきただけですが。

     

    子 Form を抽象化して、初期化の際に BindingSource を親が渡してやっている
    だけですが、それを書くのすら面倒になってきました。書き漏れも生じ始めました。

     

     

    そこで、MVC の Model に相当する機能を提供するものが .net framework に存
    在しないか探しています。どの Form からもアクセス可能であり、データ処理
    を一手に引き受けてくれるコンポーネントが欲しいと考えています。


    DataGridView を例に挙げると、

    • DataGridView は View、
    • ソース (.cs) に書かれている処理は Controller、
    • DataGridView.DataSource から先の処理は Model、

    ...のように分けられると思うのですが、DataSource から先の処理 (Model の
    受け持ち) を引き受けてくれる Form の外にあるオブジェクトが欲しいと考え
    ています。

    # DataGridView コンポーネントの中にも MVC が存在するように感じますが、

    # 今はそれを無視。


    しかし、現状は TableAdapter や DataSet が各 Form 内に、しかも個別に独立
    してあるため、データの一括処理ができないように思えます。


    データ処理を統合する作業を施せば可能なのでしょう。それを行うのが、上記
    BindingSource の共有なのでしょうが、それもちまちまとして結構面倒です。


     

    コンポーネントの分け方が想像していたのと違いますが、下記の情報が参考に
    なると思います。

    1. n 層データ アプリケーションの概要

    3 が近いかな。

     

    これらに書かれていることを今の力量で直ぐにできるとは思いませんので、

    別解として、データ処理を一括で行ってくれる Model クラスに類する機能は

    .net framework にありませんか?

     

     

    + 追記
    N-Tier の動画による説明。
    今更 VB じゃなくて VC# で欲しかったなぁ。

     

    - How Do I Use DataSets in an N-Tier Application? - Video Training

     

    2008年2月17日 3:55

回答

  • 1つのDataSetが共有できるかどうかは子Formの仕様にもよりますが・・・

     

    基本的に親FormかスタティックインスタンスでDataSetを用意します。

    3.開発ヒント6:BindingSourceコンポーネントは共通で使う

    を参照してBindingSourceを使い回ししていけば変更の同期が全Formで適用されます。

    これは現在注目中のレコードも含まれますのでこの点が問題になる画面はBindingSourceを別にします。

    2008年2月18日 14:08
  •  custarさんこんにちは

     別の画面でDataSetを共有するには、BindingSourceのDataSourceを代入し直せばよいと思います。デザイン画面では画面毎に別のインスタンスで設定しておき、共有するDataSetをプログラム上のどのフォームからも参照できる場所に記述しておき、Form_Loadなどで代入します。この方法のメリットはBindingSourceの代入に比べて代入するオブジェクトが少ないことです。欠点はPositionが同期しないことです。Positionが変更されるたびに他のBindingSourceにPositionを代入しなければなりません。OnCurrentChangedなどのイベントを活用すればよいと思います。

    2008年2月19日 3:45
  • syllabaryDataSet.Designer.cs ではなく、 syllabaryDataSet.xsd を右クリックしてコードを表示します。

    syllabaryDataSet.cs が編集できますのでそこに記述します。

    使う機能の分だけ この場合はUpdate 以下のページのように書きます。

    http://www.atmarkit.co.jp/fdotnet/vblab/developbizapp_03/developbizapp_03_01.html

     

    Namespace ユーザーDataSetTableAdapters
      Partial Public Class
    ユーザーTableAdapter
        Implements
    ISearchEditByKeysTableAdapter
        Public Sub UpdateTable(ByVal table As
    DataTable) _
            Implements
    ISearchEditByKeysTableAdapter.UpdateTable
          Me.Update(CType
    (table, ユーザーDataSet.ユーザーDataTable))
        End Sub

      End Class
    End Namespace

     

    private object _tableAdapter = null;

    これはこう書かなきゃもったいないです。

    private ITableAdapter _tableAdapter = null;

     

    Activator.CreateInstance ってTableAdapterのインスタンスを持っている、

    クラスからもらえばいいんじゃないですか?

    自分(UserControl)が持ってもいいですけどね。

    2008年2月24日 3:18

すべての返信

  • わたしの記事で申し訳ないですがぴったりだと思いますのでこちらからごらんください。

    第3回の2がぴったりな内容に見えますが、第1回から第4回まで通しで見ていただくとより分かりやすいと思います。

    http://www.atmarkit.co.jp/fdotnet/vblab/developbizapp_index/index.html

     

    P.S.親Formと子Formも親Formと子UserControlも概念は同じです。

    2008年2月17日 6:45
  • えムナウ さん、情報提供ありがとうございます。

     

    私の考えがまだ及んでいないのだと思いますが、コンポーネントとして以下の
    ような要求をしています。

    • どの Form からもアクセス可能であり、
    • データ処理を一手に引き受けてくれる

    上記記事内容を使うと、インターフェイスを使い、DataSet への問い合わせ方
    法 (メソッド) の統一は図れるかもしれません。

     

    では、「どの Form からもアクセス可能」という要求をクリアするにはどうし
    たらいいのでしょう?


     

    Form ごとに独立な DataSet が作られているのが問題だと思っているのですが、
    1つの DataSet を共有できる簡単な方法があるのでしょうか?

    2008年2月17日 7:25
  • 1つのDataSetが共有できるかどうかは子Formの仕様にもよりますが・・・

     

    基本的に親FormかスタティックインスタンスでDataSetを用意します。

    3.開発ヒント6:BindingSourceコンポーネントは共通で使う

    を参照してBindingSourceを使い回ししていけば変更の同期が全Formで適用されます。

    これは現在注目中のレコードも含まれますのでこの点が問題になる画面はBindingSourceを別にします。

    2008年2月18日 14:08
  •  custarさんこんにちは

     別の画面でDataSetを共有するには、BindingSourceのDataSourceを代入し直せばよいと思います。デザイン画面では画面毎に別のインスタンスで設定しておき、共有するDataSetをプログラム上のどのフォームからも参照できる場所に記述しておき、Form_Loadなどで代入します。この方法のメリットはBindingSourceの代入に比べて代入するオブジェクトが少ないことです。欠点はPositionが同期しないことです。Positionが変更されるたびに他のBindingSourceにPositionを代入しなければなりません。OnCurrentChangedなどのイベントを活用すればよいと思います。

    2008年2月19日 3:45
  • えムナウ さん、三輪の牛 さん、サポートありがとうございます。


     えムナウ さんからの引用

    基本的に親 Form かスタティックインスタンスで DataSet を用意します。

    3.開発ヒント6:BindingSourceコンポーネントは共通で使う

    を参照して BindingSource を使い回ししていけば変更の同期が全 Form で適用されます。

     

     三輪の牛 さんからの引用

    別の画面で DataSet を共有するには、BindingSource の DataSource を代入し
    直せばよいと思います。


    あぁ、分かった気がします。


    三輪の牛 さんの仰 (おっしゃ) ることは分かります。実際そうしています。

     

    BindingSource を共有しただけだと、「表示」は同期しますが、「保存や削除」
    はその部分のコードを見る限り同期しないと思っていました。


    以前やってたのは、

    • UserControl に DataSet を渡し、
    • それを UserControl 内の BindingSource.DataSource に代入する

    ということをやって DataSet の共有と考えていたのですが、


    Code Snippet

    public partial class UserControl1 : UserControl
    {
      ....

      private void columnsBindingNavigatorSaveItem_Click(object sender, EventArgs e)
      {
        this.Validate();
        this.columnsBindingSource.EndEdit();
        this.tableAdapterManager.UpdateAll(this.syllabaryDataSet);
      }

     

      public syllabaryDataSet DataSet
      {
        get
        {
          return this.syllabaryDataSet;
        }
        set
        {
          this.columnsBindingSource.DataSource

            = this.syllabaryDataSet

            = (syllabaryDataSet)value;
        }
      }
    }

     


    そもそも、BindingSource.DataSource = DataSet のような関係にある
    BindingSource の場合、渡された先の UserControl 内でも以下のようにすれば、
    大元の DataSet にアクセスできますね。

     

    こちらで同期するのを確認できました。


    Code Snippet

    public partial class UserControl1 : UserControl
    {
      ....

      private void columnsBindingNavigatorSaveItem_Click(object sender, EventArgs e)
      {
        this.Validate();
        this.columnsBindingSource.EndEdit();
        this.tableAdapterManager.UpdateAll(

          (syllabaryDataSet)this.columnsBindingSource.DataSource);
      }

     

      public BindingSource DataSource
      {
        get
        {
          return this.columnsBindingSource;
        }
        set
        {
          this.columnsDataGridView.DataSource

            = this.columnsBindingSource

            = (BindingSource)value;
        }
      }
    }

     

     

    タイトルと内容が変わってしまいましたが、こちらのモヤモヤはなくなりました。

    スッキリです。ありがとうございます。

     

     

    「いやいや、こうすればもっと簡単になるよ」というご意見があればお教えください。

    2008年2月19日 8:25
  • インスタンスへの参照を受け取るか、公開されているインスタンスへの参照を取りに行くかしかないでしょうから(上の例は前者)、UserControlの実装としては前者で正解だと思います。なぜなら、UserControlはどのようなものが公開されているかを事前に知ることができないからです。

    同じ意味合いで、せっかくのUserControlなのにsyllabaryDataSetという特定の型が入っているのが気になります。もっとも、限定された状況下でのUserControlであれば、これで良いという話もありだとは思います。

    もっと簡単じゃなくて、もっとコードが増えますが、インターフェースを使うともっと汎用的に実装できると思います。ご存じで蛇足かもしれませんが・・・。

    2008年2月19日 9:49
    モデレータ
  •  trapemiya さんからの引用

    せっかくの UserControl なのに syllabaryDataSet という特定の型が入ってい
    るのが気になります。


    そうなんです。あれやこれやと質問してますが、そこが引っ掛かってます。


     trapemiya さんからの引用

    もっと簡単じゃなくて、もっとコードが増えますが、インターフェースを使う
    ともっと汎用的に実装できると思います。ご存じで蛇足かもしれませんが・・・。


    そう、インターフェイスなんです。
    # 相変わらずポイントを押さえますね、trapemiya さん

     

    こんな場合、c# ではどういう構成と作りになっていくのか、是非教えてください。
    # ずばり、分かってません!

     

    コード入力の手間なんて惜しみません。
    # とか言いながら、閉じ "}" の入力を面倒に感じてます。

     

    それよりも、1週間後の自分が自分のコードを理解するのに時間を要する方が
    新しさがなく、心理的・作業的に苦痛なので。

    2008年2月19日 11:30
  • 試してませんが、以下みたいな感じでいけないでしょうか?(ドキドキ)

     

    public partial class UserControl1 : UserControl
    {
      ....
      private _BindingSourceWrapper;

     

      private void columnsBindingNavigatorSaveItem_Click(object sender, EventArgs e)
      {
        this.Validate();
        _BindingSourceWrapper.EndEdit();
        _BindingSourceWrapper.UpdateAll();
      }

     

      public IBindingSourceWrapper BindingSourceWrapper
      {
        set
        {
          _BindingSourceWrapper = value;
          this.columnsDataGridView.DataSource  = value.BindingSourceManaged;
        }
      }
    }

     

    public interface IWrapBindingSource
    {
        BindingSource BindingSourceManaged { get; }
        void EndEdit();
        void UpdateAll();
    }

     

    public class ColumnsBindingSourceWrapper : IWrapBindingSource
    {
       public BindingSource BindingSourceManaged
       {
          get
          {
              //実装
          }
        }
     
     public void EndEdit()
       {
            //実装
       }

     

     public void UpdateAll()
       {
            //実装
       }

     

     どこかで
     UserControl1 uc1 = new UserControl1();
      uc1.BindingSourceWrapper = this;
    }

    2008年2月19日 16:57
    モデレータ
  • TableAdapter まで含めてインターフェース化していかないと切り分けづらいんですよね。

    結局前回紹介したなかの以下のリンクのようになってしまいます。

    1.開発ヒント7:インターフェイスで機能を整理

     

    2008年2月20日 3:34
  •  えムナウ さんからの引用

    TableAdapter まで含めてインターフェース化していかないと切り分けづらいんですよね。


    TableAdapter が受け持つ機能は基本的な select, insert, update, delete の
    みとした場合でも、インターフェイスに含めた方がいいのですか?

     

     

    p.s.
    ----
    頭の中が、整理できてなくてごちゃごちゃしていますが、UserControl にした
    り、インターフェイスにしたりして楽になるのは、コーディングであって、

     

    開発段階では、デザインもちょくちょく変わるので、Visual な部分もデザイナ
    でいじれる実装時に拡張可能な UserControl だったらいいなぁ、とぼんやり

    感じてます。

     

    そんなの無理なんですかね。またはそんなの普通やらない、かな。

     

    まだ支離滅裂な段階だから、試行錯誤中。

    2008年2月20日 5:59
  •  えムナウ さんからの引用

    TableAdapter まで含めてインターフェース化していかないと切り分けづらいんですよね。


    確かにその通りですね。そうしなければUserControlが型付けされたTableAdapterに引きずられてしまいますから。その点、私が上で提示したコードが中途半端なのは否めません。

    2008年2月20日 6:46
    モデレータ
  •  custar さんからの引用

    TableAdapter が受け持つ機能は基本的な select, insert, update, delete の
    みとした場合でも、インターフェイスに含めた方がいいのですか?

     

    TableAdapter という抽象クラスがありませんよね。
    何かの形でselect, insert, update, delete を抽象化しないと分離はできません。

    2008年2月20日 11:43
  •  えムナウ さんからの引用

    TableAdapter という抽象クラスがありませんよね。
    何かの形でselect, insert, update, delete を抽象化しないと分離はできません。


    UserControl という独立した存在を作り出す時に、データベースとのやり取り
    を受け持つ TableAdapter が UserControl 内に要るんじゃない?、という意味
    として捉えると、TableAdapter は必要ですね。


    そう解釈していいのですか?


    仮にそうだとして、その先が分かりません。何~かピンと来ない。

    2008年2月22日 8:03
  • syllabaryDataSet は DataSet から継承されていますので DataSet というクラスで抽象化できます。

    tableAdapterManager は何から継承されていますか?
    そのクラスは TableAdapter の特徴をあらわせますか?

     

    TableAdapter を抽象化する方法はないので、interface で TableAdapter の機能を記述するとすっきりと抽象化できます。

    2008年2月22日 11:32
  • もう殆どギブアップの状態ですが、こんな感じですか?


    ITableAdapter.cs

    Code Snippet
    interface ITableAdapter
    {
      int Update(syllabaryDataSet ds);
    }

     

    TableAdapter に共通の Update() メソッドを用意する。


    syllabaryDataSet.Designer.cs

    Code Snippet

    public partial class columnsTableAdapter

      : global::System.ComponentModel.Component, ITableAdapter


    public partial class charsTableAdapter

      : global::System.ComponentModel.Component, ITableAdapter

     

    各 TableAdapter クラスの親として ITableAdapter を追加する。


     

    Element.cs

    Code Snippet

    public partial class Element : UserControl
    {

      ....

     

      private BindingSource _bindingSource = null;
      private object _tableAdapter = null;

     

      public BindingSource DataSource
      {
        get
        {
          return this._bindingSource;
        }
        set
        {
          if (value != null)
          {
            this.dataGridView.DataSource = this._bindingSource = value; ............ (1)
            string dataMember = this._bindingSource.DataMember; .................... (2)

     

            if (!string.IsNullOrEmpty(dataMember))
            {
              string klass = "columns_chars_ITableAdapter.syllabaryDataSetTableAdapters."
                + dataMember + "TableAdapter";
                                                                                .... (3)
              this._tableAdapter = Activator.CreateInstance(Type.GetType(klass));
                                                                                .... (4)
            }
          }
        }
      }

     

      private void saveBtn_Click(object sender, EventArgs e)
      {
        this.Validate();
        this._bindingSource.EndEdit();

     

        if (this._tableAdapter != null)
        {
          ((ITableAdapter)this._tableAdapter).Update(

            (syllabaryDataSet)this._bindingSource.DataSource); ..................... (5)
        }
      }
    }

     

    (1)
    BindingSource を代入。
    (2)
    DataGridView で使うテーブル名を入手。
    (3)
    (2) のテーブル名からクラス名を作成。
    (4)
    テーブル名をもつ TableAdapter を生成。
    (5)
    TableAdapter に共通する Update() を呼び出すために ITableAdapter でキャストする。
    syllabaryDataSet を使う代わりに、BindingSource.DataSource で代用。

     

     

    取り敢えず動いてますが、前述の通りギブアップに近い状態です。


    syllabaryDataSet.Designer.cs の各 TableAdapter の定義に ITableAdapter
    を追加するってのが、泥臭いなぁ、と感じてます。


    他にいい方法があれば教えてください。

    2008年2月23日 21:48
  • syllabaryDataSet.Designer.cs ではなく、 syllabaryDataSet.xsd を右クリックしてコードを表示します。

    syllabaryDataSet.cs が編集できますのでそこに記述します。

    使う機能の分だけ この場合はUpdate 以下のページのように書きます。

    http://www.atmarkit.co.jp/fdotnet/vblab/developbizapp_03/developbizapp_03_01.html

     

    Namespace ユーザーDataSetTableAdapters
      Partial Public Class
    ユーザーTableAdapter
        Implements
    ISearchEditByKeysTableAdapter
        Public Sub UpdateTable(ByVal table As
    DataTable) _
            Implements
    ISearchEditByKeysTableAdapter.UpdateTable
          Me.Update(CType
    (table, ユーザーDataSet.ユーザーDataTable))
        End Sub

      End Class
    End Namespace

     

    private object _tableAdapter = null;

    これはこう書かなきゃもったいないです。

    private ITableAdapter _tableAdapter = null;

     

    Activator.CreateInstance ってTableAdapterのインスタンスを持っている、

    クラスからもらえばいいんじゃないですか?

    自分(UserControl)が持ってもいいですけどね。

    2008年2月24日 3:18
  •  えムナウ さんからの引用

    syllabaryDataSet.Designer.cs ではなく、 syllabaryDataSet.xsd を右クリッ
    クしてコードを表示します。syllabaryDataSet.cs が編集できますのでそこに
    記述します。使う機能の分だけ この場合はUpdate 以下のページのように書きます。


    私の場合だと、各 TableAdapter で ITableAdapter を継承するようにすればい
    いだけですから、


    syllabaryDataSet.Designer.cs で追加していた ITableAdapter を消して、以
    下のようにしました。


    syllabaryDataSet.cs

    Code Snippet

    //namespace columns_chars_ITableAdapter
    //{
    //  public partial class syllabaryDataSet
    //  { }
    //}

     

    namespace columns_chars_ITableAdapter.syllabaryDataSetTableAdapters
    {
      public partial class columnsTableAdapter : ITableAdapter
      { }

      public partial class charsTableAdapter : ITableAdapter
      { }

      public partial class personsTableAdapter : ITableAdapter
      { }

      public partial class chars_personsTableAdapter : ITableAdapter
      { }
    }

     


    で、保存を確認できました。

    「おっと、それは的を外してる」という場合は、ご指摘お願いします。


     えムナウ さんからの引用

    private object _tableAdapter = null;


    これはこう書かなきゃもったいないです。


    private ITableAdapter _tableAdapter = null;


    その通りですね。キャストの必要がなくなる。

     

     えムナウ さんからの引用

    Activator.CreateInstance って TableAdapter のインスタンスを持っている、
    クラスからもらえばいいんじゃないですか? 自分 (UserControl) が持っても
    いいですけどね。


    実際は私も仰 (おっしゃ) るようにやるつもりですが、
    なるべく扱う情報を少なくしてみよう、と思ってやってみました。

    2008年2月24日 12:46
  •  custar さんからの引用

    syllabaryDataSet.Designer.cs で追加していた ITableAdapter を消して、以
    下のようにしました。

    syllabaryDataSet.Designer.cs は自動生成なのでそうじゃなきゃいいです。

     custar さんからの引用

    実際は私も仰 (おっしゃ) るようにやるつもりですが、
    なるべく扱う情報を少なくしてみよう、と思ってやってみました。

    了解です。

    DataSet や TableAdapter はデザイナで組み込んでおいたほうが楽です。

    Activator.CreateInstance はなるべくやめた方がいいですよって話でした。

    2008年2月25日 3:43
  •  えムナウ さんからの引用

    syllabaryDataSet.Designer.cs は自動生成なのでそうじゃなきゃいいです。


    そうですね。デザイナでちょっと変更したら消されてしまいますから。


     えムナウ さんからの引用

    Activator.CreateInstance はなるべくやめた方がいいですよって話でした。


    Activator.CreateInstance って何か支障があるのでしょうか?

    Activator 自体、かなりマイナーな感じがしますが。

     

    文字列からオブジェクトへの変換を可能にする、他言語の eval() に似た関数
    を求めてたら見つけました。
    2008年2月25日 4:02
  • インターフェイスの使い方が分かって、だいぶやりやすくなりました。
    えムナウ さん、ありがとうございます。


    trapemiya さんのコードを最初は見ずにやろうとしていましたが、実は横目で
    ちらちら見てました。本当に分からなくて、進まなかったから、参考にさせて
    いただきました。ありがとうございます。

     

    Grid の数が多いので、UserControl 中に収める形をしているため、相変わらず
    プロパティによる受け渡しが煩雑と感じており、参考に出していた N-Tier と
    近くなりますが、DataSet を専門に扱う別ライブラリ (DLL) とできないか、試
    行中です。asp.net mvc を参考に。
    # 捨てる可能性大。


    では、本件は、ここで閉じます。


    皆さん、ありがとうございました。

    2008年2月27日 0:53