none
DataRowの値をクラスで変更できるようにしたい RRS feed

  • 質問

  • .NET Framework 3.5
    Visual Studio 2017(可能なら2010で)

    DataRowの結果と同じ項目(フィールド)を持つクラスを用意して、DataRowの列結果をクラスに入れ込むことはできました。

    が、DataRowに格納されている値を変更したり、クラスのフィールドの値を変更すると、それぞれの同期は取れません。
    つまり双方向バインドするような形がとりたいのですが、何かいい方法はありませんでしょうか?

    .NET Frameworkのバージョンが低いせいで、サードパーティ製のフレームワーク(ReactivePropertyなど)の利用はできません。
    .NET Frameworkのバージョンを上げることも不可です。

    お知恵拝借ください。

    2017年12月2日 14:25

回答

  • フィールドでは無くプロパティにしてINotifyPropertyChangedを実装して、DataTableのColumnChangedイベントとで双方向の同期をすればいいんじゃないでしょうか。

    using System;
    using System.Collections.Generic;
    namespace ConsoleApp1
    {
         class Program
         {
              static void Main(string[] args)
              {
                   System.Data.DataTable table = new System.Data.DataTable();
                   table.Columns.Add(new System.Data.DataColumn("DataColumn1", typeof(string)));
                   table.Columns.Add(new System.Data.DataColumn("DataColumn2", typeof(string)));
                   table.Columns.Add(new System.Data.DataColumn("DataColumn3", typeof(string)));
    
                   for (int i = 0; i < 1; i++)
                   {
                        var row = table.NewRow();
                        row["DataColumn1"] = "A" + i.ToString();
                        row["DataColumn2"] = "B" + i.ToString();
                        row["DataColumn3"] = "C" + i.ToString();
                        table.Rows.Add(row);
                   }
    
                   List<Connector<System.Data.DataRow, CopyClass>> list
                       = new List<Connector<System.Data.DataRow, CopyClass>>();
    
                   foreach (System.Data.DataRow row in table.Rows)
                   {
                        CopyClass cc = new CopyClass();
                        Connector<System.Data.DataRow, CopyClass> connector
                            = new Connector<System.Data.DataRow, CopyClass>(row, cc);
                        connector.CopyFromRow();
                        list.Add(connector);
                   }
    
                   Console.WriteLine("CopyClass.DataColumn2=" + list[0].Source.DataColumn2);
                   table.Rows[0]["DataColumn2"] = "X";
                   Console.WriteLine("CopyClass.DataColumn2=" + list[0].Source.DataColumn2);
    
                   Console.WriteLine("*********************");
    
                   Console.WriteLine("Row[\"DataColumn3\"]=" + table.Rows[0]["DataColumn3"]);
                   list[0].Source.DataColumn3 = "Y";
                   Console.WriteLine("Row[\"DataColumn3\"]=" + table.Rows[0]["DataColumn3"]);
    
                   Console.ReadKey();
              }
         }
    
         class Connector<ROW, SOURCE> : IDisposable
              where ROW : System.Data.DataRow
              where SOURCE : System.ComponentModel.INotifyPropertyChanged
         {
              static Connector()
              {
                   var t = typeof(SOURCE);
                   foreach (System.ComponentModel.PropertyDescriptor pd in System.ComponentModel.TypeDescriptor.GetProperties(t))
                   {
                        pds.Add(pd.Name, pd);
    pd.SetValue(
                   }
              }
              private static Dictionary<string, System.ComponentModel.PropertyDescriptor> pds
                  = new Dictionary<string, System.ComponentModel.PropertyDescriptor>();
    
    
              public ROW Row { get { return _Row; } }
              private ROW _Row;
    
              public SOURCE Source { get { return _Source; } }
              private SOURCE _Source;
    
    
              public Connector(ROW row, SOURCE target)
              {
                   this._Source = target;
                   this.Source.PropertyChanged += Source_PropertyChanged;
    
                   this._Row = row;
                   this._Row.Table.ColumnChanged += Table_ColumnChanged;
              }
    
              private void Source_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
              {
                   var pd = pds[e.PropertyName];
                   object v = pd.GetValue(this._Source);
                   var d = _Row[pd.Name];
                   if (d == DBNull.Value)
                   {
                        d = null;
                   }
                   if (!object.Equals(v, d))
                   {
                        _Row[pd.Name] = v;
                   }
              }
    
              private void Table_ColumnChanged(object sender, System.Data.DataColumnChangeEventArgs e)
              {
                   if (e.Row == this.Row)
                   {
                        System.ComponentModel.PropertyDescriptor pd;
                        if (pds.TryGetValue(e.Column.ColumnName, out pd))
                        {
                             object v = pd.GetValue(this.Source);
                             if (!object.Equals(v, e.ProposedValue))
                             {
                                  pd.SetValue(this.Source, e.ProposedValue);
                             }
                        }
                   }
              }
    
              public void CopyFromSource()
              {
                   foreach (System.ComponentModel.PropertyDescriptor pd in pds.Values)
                   {
                        object v = pd.GetValue(this._Source);
                        var d = _Row[pd.Name];
                        if (d == DBNull.Value)
                        {
                             d = null;
                        }
                        if (!object.Equals(v, d))
                        {
                             _Row[pd.Name] = v;
                        }
                   }
              }
              public void CopyFromRow()
              {
                   foreach (System.ComponentModel.PropertyDescriptor pd in pds.Values)
                   {
                        object v = pd.GetValue(this._Source);
                        object d = _Row[pd.Name];
                        if (d == DBNull.Value)
                        {
                             d = null;
                        }
                        if (!object.Equals(v, d))
                        {
                             pd.SetValue(_Source, _Row[pd.Name]);
                        }
                   }
              }
    
              private void DisConnect()
              {
                   if (this.Row != null)
                   {
                        this._Row.Table.ColumnChanged -= Table_ColumnChanged;
                        this._Row = null;
                   }
                   if (this._Source != null)
                   {
                        this.Source.PropertyChanged -= Source_PropertyChanged;
                        this._Source = default(SOURCE);
                   }
              }
    
    
    
              public void Dispose()
              {
                   DisConnect();
              }
         }
    
         class CopyClass : System.ComponentModel.INotifyPropertyChanged
         {
              public string DataColumn1 { get { return _DataColumn1; } set { _DataColumn1 = value; OnPropertyChanged("DataColumn1"); } }
              private string _DataColumn1;
              public string DataColumn2 { get { return _DataColumn2; } set { _DataColumn2 = value; OnPropertyChanged("DataColumn2"); } }
              private string _DataColumn2;
              public string DataColumn3 { get { return _DataColumn3; } set { _DataColumn3 = value; OnPropertyChanged("DataColumn3"); } }
              private string _DataColumn3;
    
              public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
              protected virtual void OnPropertyChanged(string name)
              {
                   var pc = PropertyChanged;
                   if (pc != null)
                   {
                        pc(this, new System.ComponentModel.PropertyChangedEventArgs(name));
                   }
              }
         }
    }
    

    #どうしてもINotifyPropertyChandedをつかいたくないなら、DependencyPropertyにしたり、PropertyDescrpitor経由で値をセットするというめんどくさい方法でも可能ですが。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年12月2日 17:19

すべての返信

  • フィールドでは無くプロパティにしてINotifyPropertyChangedを実装して、DataTableのColumnChangedイベントとで双方向の同期をすればいいんじゃないでしょうか。

    using System;
    using System.Collections.Generic;
    namespace ConsoleApp1
    {
         class Program
         {
              static void Main(string[] args)
              {
                   System.Data.DataTable table = new System.Data.DataTable();
                   table.Columns.Add(new System.Data.DataColumn("DataColumn1", typeof(string)));
                   table.Columns.Add(new System.Data.DataColumn("DataColumn2", typeof(string)));
                   table.Columns.Add(new System.Data.DataColumn("DataColumn3", typeof(string)));
    
                   for (int i = 0; i < 1; i++)
                   {
                        var row = table.NewRow();
                        row["DataColumn1"] = "A" + i.ToString();
                        row["DataColumn2"] = "B" + i.ToString();
                        row["DataColumn3"] = "C" + i.ToString();
                        table.Rows.Add(row);
                   }
    
                   List<Connector<System.Data.DataRow, CopyClass>> list
                       = new List<Connector<System.Data.DataRow, CopyClass>>();
    
                   foreach (System.Data.DataRow row in table.Rows)
                   {
                        CopyClass cc = new CopyClass();
                        Connector<System.Data.DataRow, CopyClass> connector
                            = new Connector<System.Data.DataRow, CopyClass>(row, cc);
                        connector.CopyFromRow();
                        list.Add(connector);
                   }
    
                   Console.WriteLine("CopyClass.DataColumn2=" + list[0].Source.DataColumn2);
                   table.Rows[0]["DataColumn2"] = "X";
                   Console.WriteLine("CopyClass.DataColumn2=" + list[0].Source.DataColumn2);
    
                   Console.WriteLine("*********************");
    
                   Console.WriteLine("Row[\"DataColumn3\"]=" + table.Rows[0]["DataColumn3"]);
                   list[0].Source.DataColumn3 = "Y";
                   Console.WriteLine("Row[\"DataColumn3\"]=" + table.Rows[0]["DataColumn3"]);
    
                   Console.ReadKey();
              }
         }
    
         class Connector<ROW, SOURCE> : IDisposable
              where ROW : System.Data.DataRow
              where SOURCE : System.ComponentModel.INotifyPropertyChanged
         {
              static Connector()
              {
                   var t = typeof(SOURCE);
                   foreach (System.ComponentModel.PropertyDescriptor pd in System.ComponentModel.TypeDescriptor.GetProperties(t))
                   {
                        pds.Add(pd.Name, pd);
    pd.SetValue(
                   }
              }
              private static Dictionary<string, System.ComponentModel.PropertyDescriptor> pds
                  = new Dictionary<string, System.ComponentModel.PropertyDescriptor>();
    
    
              public ROW Row { get { return _Row; } }
              private ROW _Row;
    
              public SOURCE Source { get { return _Source; } }
              private SOURCE _Source;
    
    
              public Connector(ROW row, SOURCE target)
              {
                   this._Source = target;
                   this.Source.PropertyChanged += Source_PropertyChanged;
    
                   this._Row = row;
                   this._Row.Table.ColumnChanged += Table_ColumnChanged;
              }
    
              private void Source_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
              {
                   var pd = pds[e.PropertyName];
                   object v = pd.GetValue(this._Source);
                   var d = _Row[pd.Name];
                   if (d == DBNull.Value)
                   {
                        d = null;
                   }
                   if (!object.Equals(v, d))
                   {
                        _Row[pd.Name] = v;
                   }
              }
    
              private void Table_ColumnChanged(object sender, System.Data.DataColumnChangeEventArgs e)
              {
                   if (e.Row == this.Row)
                   {
                        System.ComponentModel.PropertyDescriptor pd;
                        if (pds.TryGetValue(e.Column.ColumnName, out pd))
                        {
                             object v = pd.GetValue(this.Source);
                             if (!object.Equals(v, e.ProposedValue))
                             {
                                  pd.SetValue(this.Source, e.ProposedValue);
                             }
                        }
                   }
              }
    
              public void CopyFromSource()
              {
                   foreach (System.ComponentModel.PropertyDescriptor pd in pds.Values)
                   {
                        object v = pd.GetValue(this._Source);
                        var d = _Row[pd.Name];
                        if (d == DBNull.Value)
                        {
                             d = null;
                        }
                        if (!object.Equals(v, d))
                        {
                             _Row[pd.Name] = v;
                        }
                   }
              }
              public void CopyFromRow()
              {
                   foreach (System.ComponentModel.PropertyDescriptor pd in pds.Values)
                   {
                        object v = pd.GetValue(this._Source);
                        object d = _Row[pd.Name];
                        if (d == DBNull.Value)
                        {
                             d = null;
                        }
                        if (!object.Equals(v, d))
                        {
                             pd.SetValue(_Source, _Row[pd.Name]);
                        }
                   }
              }
    
              private void DisConnect()
              {
                   if (this.Row != null)
                   {
                        this._Row.Table.ColumnChanged -= Table_ColumnChanged;
                        this._Row = null;
                   }
                   if (this._Source != null)
                   {
                        this.Source.PropertyChanged -= Source_PropertyChanged;
                        this._Source = default(SOURCE);
                   }
              }
    
    
    
              public void Dispose()
              {
                   DisConnect();
              }
         }
    
         class CopyClass : System.ComponentModel.INotifyPropertyChanged
         {
              public string DataColumn1 { get { return _DataColumn1; } set { _DataColumn1 = value; OnPropertyChanged("DataColumn1"); } }
              private string _DataColumn1;
              public string DataColumn2 { get { return _DataColumn2; } set { _DataColumn2 = value; OnPropertyChanged("DataColumn2"); } }
              private string _DataColumn2;
              public string DataColumn3 { get { return _DataColumn3; } set { _DataColumn3 = value; OnPropertyChanged("DataColumn3"); } }
              private string _DataColumn3;
    
              public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
              protected virtual void OnPropertyChanged(string name)
              {
                   var pc = PropertyChanged;
                   if (pc != null)
                   {
                        pc(this, new System.ComponentModel.PropertyChangedEventArgs(name));
                   }
              }
         }
    }
    

    #どうしてもINotifyPropertyChandedをつかいたくないなら、DependencyPropertyにしたり、PropertyDescrpitor経由で値をセットするというめんどくさい方法でも可能ですが。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2017年12月2日 17:19
  • ありがとうございます。

    書いてくださったコードを見ていて、なるほどとなりました。
    INotifyPropertyChangedの実装について自前で行うことは難しくて避けていましたが、少しわかった気がします。

    元々DataRowの拡張メソッドでやろうとしてたのですが、DataTableのColumnChangedにも実装しなければならないことを気づかされたため、根本から考え直そうかと思います。
    (所々で何度も同じような処理をされてしまい、同じイベントが何度も走りそうなので)

    2017年12月3日 14:23
  • そのクラスとDataRowは、常に同期が取れていなくても良いように思います。
    詳細な仕様がわかりませんが、もし、DataRowとデータベースでやり取りが発生するのであれば、そのクラスに最初に読み込む時と、最後にデータベースに書き戻す時のみ、基本的にはデータベースとDataRow間のやり取りをすれば良いように思います。そのクラスと常に同期が取れていないといけないのは、もし、そのクラスを画面表示に使っているのであれば、そのクラスと画面表示との間です。それにそのクラスとDataRowが常に同期が取れているのであれば、基本的にはDataRowだけあればよく、そのクラスの存在意味は薄まると思います。

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2017年12月4日 2:50
    モデレータ
  • 現在やりたいことは、画面はありません。
    あるクエリからDataTableを得て、様々な条件から値を変更し、DataAdapterのUpdateでDB更新するイメージです。

    『様々な条件から値を変更し』の部分が、コードでごりごり書くようになるんですが、以下を回避したいと思っています。
     ・DataTableやDataRowの意識は、ループで取得する時くらいに済ませたい。
     ・登録、更新処理においてループしながら1件ずつクエリを発行するような実装は避けたい。
     ・項目名のtypoを防ぎたい。
    (発行されるクエリはINSERTもしくはUPDATEだけ)

    ただ、あまり時間がないため、あらゆるパターンまで想定した仰々しい感じにはしたくないというのが本音です。
    また、サードパーティ製のツールの制約もあったりして調査コストから更にかさんでしまうため、確認が取れていないDataTable以外の別な方法論は考えていません。

    考えていたイメージだと、DataTableから特定のDataRowを取り出したタイミングでクラスにマッピングされ、そのクラス内の値を変更すればDataRowにも反映される。
    更に別なDataRowを取り出して同じようにできるイメージでした。
    (上記をなんとかしたいだけなので、EFのように、クラスがDbSetなどでリスト化するレベルまで管理しない)

    そのため、マッピングされたクラスを保持し続けるイメージではありませんでした。

    しかし、値が確定した段階でDatatRowに戻すことを開発する上でのルール付けとすれば、教えていただいた方法でも
    可能かなと思いましたので、それだけで賄えるかどうかも含めて少し考えてみます。
    ありがとうございました。

    2017年12月4日 4:23