QA@IT

DataViewで子テーブルの列データによる親テーブルのフィルター

4632 PV

こんにちは、VC#.NETでデータベースを使ったアプリケーション開発に従事している者です。
早速ですが質問させてください。

リレーションを設定した複数のテーブルがあるとします。
仮に、

親テーブル名を ParentTable
子テーブル名を ChildTable

とします。
各テーブルのカラムおよびリレーションシップ名は以下のように設定します。

ParentTable.ID    リレーションシップの親列
ParentTable.Name

ChildTable.ParentID    リレーションシップの子列
ChildTable.Price

FK_Parent_Child    リレーションシップ名

以上が前提となりまして、ここからが本題となります。

DataView を作成しその Tableプロパティ に ParentTable を設定します。

次に、その DataView に RowFilterプロパティ にてフィルターを設定するのですが、
その際に子テーブル(ChildTable)の列データによるフィルターをかけたいのです。

DataView dv = new DataView();
dv.Table = ParentTable;
dv.RowFilter = ???    ←ここの設定方法がわかりません。

以下のように親テーブルの列データ(Name列)によるフィルターならもちろんわかります。

dv.RowFilter = "Name = 'パソコン'";

しかし、子テーブルの列データ(Price列)によるフィルター方法がわかりません。

dv.RowFilter = "ChildTable.Price = 100000";    ←ダメ
dv.RowFilter = "FK_Parent_Child.Price = 100000";    ←これもダメ

そもそも子テーブルの列データによるフィルターというのは可能なのでしょうか?
子テーブルのデータで親テーブルにフィルターをかける、という発想自体が間違えているのでしょうかか?

初心者故に見当外れな内容を書いているかも知れません。ご教授頂ければ幸いです。
よろしくお願い致します。

  • C#のバージョン、またはVisual Studioのバージョンはなんでしょうか -

回答

追記しました

すいません、RowsFilterにリレーション指定できるの忘れてました。
ただし、親から子へのリレーションは集約関数かなにかで件数絞る必要があったはずです。

たとえば子から親へのリレーションを利用する場合は簡単で

ChildTableのDataViewを作成し、
dv.RowsFilter="Parent(FK_Parent_Child).Name = 'パソコン'";
などとすると、FK_Parent_Childリレーションにおける、親テーブルのNameが パソコン という条件を付けることができます。
Parent(FK_Parent_Child)Parentはキーワードです。ParentTableだからではありません。)

ところが逆にParentTableのDataViewを作成して
dv.RowsFilter="Child(FK_Parent_Child).Price < 4000";
とした場合、Childが 1件に定まっていないのでエラーになります。
なのでたとえば

dv.RowsFilter="SUM(Child(FK_Parent_Child).Price) < 4000";
の様に集約関数などで 1件にしなければ、「トークン'Child'を変換できません」エラーになります。


集計関数を使用しない場合はちょっと思いつかないので、私ならLinqにしてしまいます。
あとはそもそもSQLで絞り込むかですね。基本的にDataTableに大量のデータを取得すべきではありませんので。

var rs = ChildTable
    .Select("Price < 4000 or Price = 30000")
    .AsEnumerable()
    .Select(x => {
        foreach (DataRow r in x.GetParentRows("FK_Parent_Child")) {
            return r;
        }
        return null;
    })
    .Distinct();

foreach (var r in rs) {
    System.Diagnostics.Debug.Print(r["Name"]);
}

またDataViewが欲しいというケースであれば、このままDataTableをもう一個作ってしまい、そこにインポートしてしまいます。

var newDt = ParentTable.Clone();
foreach (DataRow r in rs){
  newDt.ImportRow(r);
}

// var newView = newDt.DefaultView;
// var newView = new DataView(newDt);

以下追記前のフィルターではなくGetParentRowを使用する方法

ちなみにですが、型付データセットの話でしょうか?EntityFrameworkかな?

DataTableに設定されているリレーションを使用する場合は
子の行からGetParentRowやGetParentRowsなどを使って親の行を取ることができます。

これについてChildテーブルのPriceが4000未満のParentTableのアイテムを取る場合、

var parentRows = new List<DataRow>();

foreach(DataRow r in ChildTable.Select("Price < 4000")){
  parentRows.Add(r.GetParentRow("FK_Parent_Child"));
}

foreach (var r in parentRows)
{
    System.Diagnostics.Debug.Print(r["Name"].ToString());
}

と、ChildTableをSelectして、各行の親の行を取得します。
正確にはGetParentRowsで多対多にも備えるべきですが、コードが長くなるので割愛。
同じ親をとるChildTableの行がいる場合は複数個とれていることに注意してください。

ところで、最近ですとLinqも使ったりするわけですがそうすると以下の様になります。

var pRows = ChildTable.Select("Price < 4000")
    .AsEnumerable()
    .Select(x => x.GetParentRow("FK_Parent_Child"))
    .Distinct();

foreach (var p in pRows)
{
    System.Diagnostics.Debug.Print(p["Name"].ToString());
}

最初型付データセットで書いてたので変な部分があるかもしれません。

編集 履歴 (4)
  • flied_onionさん、ご回答ありがとうございます。

    開発環境は.NET Framework 4になります。
    データベースとはDataSetを介してデータの受け渡しをしています。
    -
  • flied_onionさん、ご回答の方法で子テーブルのカラムデータによるフィルターをかけることが出来ました。

    しかし、文字列や日付での抽出だとSUM関数などを使えないのですが、この場合は何らか他の方法があるのでしょうか?
    -
  • その場合はGetParentRowsを利用するしかないように思います。 -
  • 回答にサンプルを追記しました。 -
  • flied_onionさん、ありがとうございました。DataViewのフィルター一発で何でも出来る、ってわけにはいかないものなんですね。色々と勉強になりました。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。