C#でLinq to objectの結果をDataTableにしてみた
SQLite3のデータを読み込んだDataTableからLinq to objectでフィルタまたは結合した結果を、DataTableに入れてみました。そのメモです。SQLiteには非接続型のデータアクセスをしてるので、SQLiteに限った話ではないです。
目次
環境
Visual Studio 2015 Express for Windows Desktop (C#)
CopyToDataTableメソッドを使う
Linqのクエリの結果をIEnumerableで得られる場合は、 `DataTableExtensions.CopyToDataTable()メソッド <https://msdn.microsoft.com/ja-jp/library/bb396189%28v=vs.110%29.aspx>`_でDataTableが得られます。
例えば、hogehogeというDataTableのField2という列にpiyoという文字列が含まれる行(レコード)を抜き出したDataTableを作りたい場合は、下記のようにします。
IEnumerable<DataRow> query =
from p in hogehoge.AsEnumerable()
where p.Field<string>("field2").Contains("piyo")
select p;
DataTable ViewTable = query.CopyToDataTable<DataRow>();
結合結果格納用のDataTableを使う
Linq to objectで結合(JOIN)した場合ですが、その結果をIEnumerableにする方法が分りませんでした。(勉強不足)
ということで、新しいDataTableを定義して結合結果を入れてみました。例えば、下記の2つのDataTableがあるとします。
テーブル名 hogehoge フィールド id, fieldA, fieldB
テーブル名 piyopiyo フィールド id, fieldC
hogehogeのfieldBとpiyopiyoのidがリンクするとして、hogehogeとpiyopiyoを結合します。結合した結果を入れるDataTableをrとします。結合結果用のDataTableにフィールドの定義をするには、 DataColumnクラス のインスタンスを作成してそのインスタンスのプロパティにフィールドの属性を定義し、 DataColumnCollection.Add()メソッド でDataTableに追加します。フィールド毎にこれを繰り返します。そして、Linqのクエリの結果をforeachでDataTableに追加します。
// 結合の結果を保持するテーブルの作成
DataTable r = new DataTable();
DataColumn column = new DataColumn();
column.DataType = System.Type.GetType("System.Int64");
column.ColumnName = "id";
r.Columns.Add(column);
column = new DataColumn();
column.DataType = System.Type.GetType("System.String");
column.ColumnName = "fieldA";
r.Columns.Add(column);
column = new DataColumn();
column.DataType = System.Type.GetType("System.String");
column.ColumnName = "fieldC";
r.Columns.Add(column);
// 結合を実行する
var query =
from p in hogehoge.AsEnumerable()
join q in piyopiyo.AsEnumerable()
on p.Field<Int64>("fieldB") equals q.Field<Int64>("id")
select new
{
id = p.Field<Int64>("id"),
fieldA = p.Field<string>("fieldA"),
fieldC = q.Field<string>("fieldC")
};
DataRow row;
foreach (var s in query)
{
row = r.NewRow();
row["id"] = s.id;
row["fieldA"] = s.fieldA;
row["fieldC"] = s.fieldC;
r.Rows.Add(row);
}
何か力技な感じ。
試してみた
アプリの概要
2つのテーブルを持つSQLiteのデータベースファイルからデータを読み込んで、下記をするアプリを作ってみました。
DataGridに、それぞれのテーブルを表示する。
DataGridに、「山」を含む短歌を表示する。
DataGridに、短歌と作者を合わせて表示する。
元のデータは、下図の様なものです。(各テーブルをPupSQLiteで表示したもの。)
poemテーブルのpoetフィールドとpoet_masterフィールドのidフィールドがリレーションするわけです。
アプリの準備
SQLiteのデータにアクセスするので、NuGetでSystem.Data.SQLiteをインストールします。
また、MVVMっぽく作りたいので、ソリューションにcommonというフォルダを作ってそこに以前使ったViewModelBase.csとDelegateCommand.csをコピーします。
ウィンドウのレイアウトをする(Viewを作る)
ウィンドウに、DataGridを1つとbuttonを4つ配置します。
XAMLを下記のようにします。
<Window x:Class="linq_trial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:linq_trial"
mc:Ignorable="d"
Title="MainWindow" Height="480" Width="640">
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Path=ViewTable, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="312" Width="615"/>
<Button x:Name="buttonShowPoemTable" Command="{Binding Path=ShowPoemTable}" Content="Poemテーブル" HorizontalAlignment="Left" Margin="90,424,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="buttonShowPoetTable" Command="{Binding Path=ShowPoetTable}" Content="Poetテーブル" HorizontalAlignment="Left" Margin="170,424,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="buttonFilter" Command="{Binding Path=FilterPoem}" Content="山でフィルタ" HorizontalAlignment="Left" Margin="250,424,0,0" VerticalAlignment="Top" Width="75"/>
<Button x:Name="buttonJoin" Command="{Binding Path=JoinData}" Content="JOIN" HorizontalAlignment="Left" Margin="330,424,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
自動生成されたXAMLに対して変更したのは、下記の点です。
Button要素のContent属性(ボタンに表示される文字列)の変更
Button要素のCommand属性の追加(コマンドバインディング)
DataGrid要素のItemSource属性の追加(データバインディング)
ViewModelを作る
ViewModel(MainWindowVM.cs)を下記のようにしました。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using System.Data;
namespace linq_trial
{
public class MainWindowVM : ViewModelBase
{
public DataTable ViewTable { get; set; } // DataGridにバインディングするDataTable
public DataTable PoemTable { get; set; } // poemを保持するためのDataTable
public DataTable PoetTable { get; set; } // poet_masterを保持するためのDataTable
// poemテーブルを表示する
private ICommand _ShowPoemTable;
public ICommand ShowPoemTable
{
get
{
if (_ShowPoemTable == null)
{
_ShowPoemTable = new DelegateCommand(ExecuteShowPoemTable);
}
return _ShowPoemTable;
}
}
private void ExecuteShowPoemTable()
{
ViewTable = PoemTable;
RaisePropertyChanged("ViewTable");
}
// poet_masterテーブルを表示する
private ICommand _ShowPoetTable;
public ICommand ShowPoetTable
{
get
{
if (_ShowPoetTable == null)
{
_ShowPoetTable = new DelegateCommand(ExecuteShowPoetTable);
}
return _ShowPoetTable;
}
}
private void ExecuteShowPoetTable()
{
ViewTable = PoetTable;
RaisePropertyChanged("ViewTable");
}
// poemテーブルのうち、「山」という文字が入っている短歌だけ表示する
private ICommand _FilterPoem;
public ICommand FilterPoem
{
get
{
if (_FilterPoem == null)
{
_FilterPoem = new DelegateCommand(ExecuteFilterPoem);
}
return _FilterPoem;
}
}
private void ExecuteFilterPoem()
{
IEnumerable<DataRow> query =
from p in PoemTable.AsEnumerable()
where p.Field<string>("body").Contains("山")
select p;
ViewTable = query.CopyToDataTable<DataRow>();
RaisePropertyChanged("ViewTable");
}
// poemテーブルとpoet_masterテーブルを結合して表示する
private ICommand _JoinData;
public ICommand JoinData
{
get
{
if (_JoinData == null)
{
_JoinData = new DelegateCommand(ExecuteJoinData);
}
return _JoinData;
}
}
private void ExecuteJoinData()
{
// 結合の結果を保持するテーブルの作成
DataTable r = new DataTable();
DataColumn column = new DataColumn();
column.DataType = System.Type.GetType("System.Int64");
column.ColumnName = "id";
r.Columns.Add(column);
column = new DataColumn();
column.DataType = System.Type.GetType("System.String");
column.ColumnName = "body";
r.Columns.Add(column);
column = new DataColumn();
column.DataType = System.Type.GetType("System.String");
column.ColumnName = "name";
r.Columns.Add(column);
// 結合を実行する
var query =
from p in PoemTable.AsEnumerable()
join q in PoetTable.AsEnumerable()
on p.Field<Int64>("poet") equals q.Field<Int64>("id")
select new
{
id = p.Field<Int64>("id"),
body = p.Field<string>("body"),
poet = q.Field<string>("name")
};
DataRow row;
foreach (var s in query)
{
row = r.NewRow();
row["id"] = s.id;
row["body"] = s.body;
row["name"] = s.poet;
r.Rows.Add(row);
}
ViewTable = r;
RaisePropertyChanged("ViewTable");
}
}
}
バインディング用に各プロパティを作成し、ボタンとバインドしているプロパティについてはgetアクセサーからDelegateCommand()で操作を実行するメソッドを呼び出します。各ボタン用実行メソッド内でViewTableプロパティ(DataGridにデータバインドしたプロパティ)にDataTableを渡して、RaisePropertyChanged()メソッドでPropertyChangedイベントを発行します。そうすると、DataGridの表示内容が書き換わります。
Modelを作る
本当はModel用のクラスを作らないとMVVMとは名乗れないと思いますが、MainWindow.xamlのコードビハインド(MainWindow.xaml.cs)に書いてしまいました。
using System;
using System.Windows;
using System.Data;
using System.Data.Common;
namespace linq_trial
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// ViewModelのインスタンスを作成し、Viewにバインディングする
MainWindowVM vm = new MainWindowVM();
this.DataContext = vm;
// データを保持するDataTableの作成
DataTable poemTable = new DataTable();
DataTable poetTable = new DataTable();
// データの読み込み
try
{
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SQLite");
using (DbConnection connection = factory.CreateConnection())
{
connection.ConnectionString = "Data Source=poemData.db"; // ファイル名を指定する
using (connection)
{
// コマンドを作る
DbCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM poem";
command.CommandType = CommandType.Text;
command.Connection = connection;
// DataAdapterを作る
DbDataAdapter adapter = factory.CreateDataAdapter();
adapter.SelectCommand = command;
// 読み込み
adapter.Fill(poemTable);
// コマンドを作る
DbCommand command2 = connection.CreateCommand();
command2.CommandText = "SELECT * FROM poet_master";
command2.CommandType = CommandType.Text;
command2.Connection = connection;
// DataAdapterを作る
DbDataAdapter adapter2 = factory.CreateDataAdapter();
adapter2.SelectCommand = command2;
// 読み込み
adapter2.Fill(poetTable);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// ViewModelにデータを渡す
vm.PoemTable = poemTable;
vm.PoetTable = poetTable;
}
}
}
内容はコード内のコメントの通りです。データ読み込み部分を別のクラスにしようとしたのですが、インスタンス間のデータ受け渡しがなかなかうまくいかず。精進が必要ですね。
動かしてみた
「poemテーブル」ボタンを押した状態です。
「poetテーブル」ボタンを押した状態です。
「山でフィルタ」ボタンを押した状態です。
「JOIN」ボタンを押した状態です。
期待通り動いてくれました。
まとめ
表示のためだけにDataTableを作るのはリソースの無駄というかDataViewを使うのが正しい道なのかもしれません。Linqは初めて使ってみました。慣れると便利かも。
公開日
広告
C#でデータ処理カテゴリの投稿
- C#でDataAdapterを使ってAccessのデータベースを読み書きしてみた
- C#でDataAdapterを使ってAccessのデータベースを読み書きしてみた(OleDb編)
- C#でDataGridに表示するデータを操作してみた
- C#でDataTableのカラムのデータ型を読んでみた
- C#でDataTableの自動インクリメントをしてみた
- C#でJSON形式のデータを出力してみた
- C#でLinq to objectの結果をDataTableにしてみた
- C#でSQLite3のデータベースを使ってみる(ADO.NET ファクトリーデザインパターン)
- C#でSQLiteを使ってみる(インストール)
- C#でコレクションの要素の数を調べる
- C#でコレクションを内部結合してみた
- C#でコレクションを左外部結合してみた
- C#でコレクションを昇順または降順に並び替える
- C#で渡された配列を加工して配列で返す関数について注意すること
- C#のキューを試してみた
- C#のスタックを試してみた