.NET Framework固有のTipsです。
アプリケーションの設定値
要約
- ugingに、「System.Configuration」を追加
using System.Configuration;
- プロジェクトの設定(ソリューションエクスプローラで、プロジェクトを右クリック→プロパティ→設定タブクリック)で、設定値のプロパティを作成する。この時に既定値も指定できる。
- データの取得は、Properties.Settings.Default.プロパティ名
string s = Properties.Settings.Default.test1;
Properties.Settings.Default.test1 = "hoge";
Properties.Settings.Default.Save();
※Saveメソッドを実行しないと保存されない。
- 設定ファイルの位置は、「Documents and Settings\<ユーザ名>\Local Settings\Application Data\<会社名>\<アセンブリ名_ハッシュ値>\<アセンブリバージョン>\user.config」となるが、会社名を指定しなかった場合は、アセンブリ名が使われる。
DBアクセス(ADO.NET 2.0)
様々なデータベースに対し、統一的に処理を行うために、冗長な手順を踏む必要がある。そのため、どうしても、とっつきにくい構造になってしまっている。
ただし、JDBCも同じような構造のプログラムになっているので、一つ覚えてしまえば、つぶしがが効くとも言える。
接続情報などは、コードに直接記述するのではなく、アプリケーションの設定値を利用するのが望ましい。
小規模なデータで、リレーションを行わないような場合は、DBを使うよりも、XMLを使用して、XPathで検索したほうが手軽な気がする。
使用するクラス
システムに登録されているDbProviderFactoryインスタンスを管理しているクラス
プロバイダ情報を格納し、下記クラスのインスタンスを作成するためのクラス
JDBCのjava.sql.DriverManagerみないなもの??
データベースへの接続状態を表すクラス
DbProviderFactoryから取得する
JDBCのjava.sql.Connectionに相当する
DBに対して実行する命令(SQL文など)を表すクラス
DbProviderFactoryから取得する
JDBCのjava.sql.PreparedStatementに相当する
DBに対する問い合わせ結果を表すクラス
DbCommand.ExceuteQueryから取得する
JDBCのjava.sql.ResultSetに相当する
DBに対する命令、接続、結果データの格納を表すクラス
DbCommandとDataSetの橋渡し役
メモリ上に展開された、データを階層的に管理するクラス
DBの問い合わせ結果などのデータを格納(データはDbDataAdapterから得られる)し、問い合わせ処理に問題が無ければ、1つ以上のDataTableクラスオブジェクトを持つ事になる
なお、問い合わせ結果が1万行あれば、1万行分のデータをメモリに展開する
一つのテーブルに対するデータを格納するクラス
列情報を格納するクラス
テーブル内の全ての列情報を格納するクラス
DataColumnのコレクション
行情報を格納するクラス
テーブル内の全ての行情報を格納するクラス
DataRowのコレクション
流れ(問い合わせの場合)
// DbProviderFactoryのインスタンスを作成
DbProviderFactory dbProvider = DbProviderFactories.GetFactory("System.Data.OleDb");
// DbProviderFactoryのインスタンスからDbConnectionを取得
DbConnection dbCon = dbProvider.CreateConnection();
// DbConnectionに、ConnectionStringを設定し、Openする
// DBファイルはtest.mdb(Microsoft Access MDB)
dbCon.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data source=test.mdb";
dbCon.Open();
// この時点で、DBとの接続が完了
// DbProviderFactoryのインスタンスからDbCommandを取得
DbCommand dbCmd = dbProvider.CreateCommand();
// DbCommandに、接続情報(DbConnection)をセット
dbCmd.Connection = dbCon;
// DbCommandに、問い合わせ文をセット(サンプルなので適当なselect文)
dbCmd.CommandText = "select * from foo";
// DbDataAdapterを取得
DbDataAdapter dbAdap = dbProvider.CreateDataAdapter();
// DbDataAdapterに、DbCommandを結びつける
dbAdap.SelectCommand = dbCmd;
// 問い合わせ結果を受け取るための、DataSetクラスのインスタンスを作成する
DataSet ds = new DataSet();
// 問い合わせ結果を取得する
dbAdap.Fill(ds);
// DataSetは複数のテーブルに対するデータが保存できるので、
// 1つ以上のテーブルデータが保存されているか確認する
if (ds.Tables.Count > 0)
{
// 最初のテーブル情報を利用する
DataTable dt = ds.Tables[0];
// 問い合わせ結果が1行以上存在するか確認する
DataRowCollection rows = dt.Rows;
if (rows.Count > 0)
{
// 問い合わせ結果の最初の行の、最初の列の値を出力する
System.Console.WriteLine(rows[0][0]);
// 問い合わせ結果の最初の行の、列名が「FIELD1」の列の値を出力する
System.Console.WriteLine(rows[0]["FIELD1"]);
}
}
// 接続を切断する
dbCon.Close();
ということらしい…
DataSetとDbDataReader
- DataSetは、全ての問い合わせ結果をメモリ上にコレクション的に格納する
- DbDataReaderは、データベースのカーソル的な動作を行う
上記のDataSetを使用した問い合わせ処理
// DbCommandに、問い合わせ文をセット(サンプルなので適当なselect文)
dbCmd.CommandText = "select * from foo";
// DbDataAdapterを取得
DbDataAdapter dbAdap = dbProvider.CreateDataAdapter();
// DbDataAdapterに、DbCommandを結びつける
dbAdap.SelectCommand = dbCmd;
// 問い合わせ結果を受け取るための、DataSetクラスのインスタンスを作成する
DataSet ds = new DataSet();
// 問い合わせ結果を取得する
dbAdap.Fill(ds);
をDbDataReaderを使用すると
// DbCommandに、問い合わせ文をセット(サンプルなので適当なselect文)
dbCmd.CommandText = "select * from foo";
// 問い合わせ結果を取得する
DbDataReader dbReader = cmd.ExecuteReader();
// 一行ずつ処理を行う
do
{
while (dbReader.Read())
{
// 列情報を取得する
// 列名では取得できない?
// 実際の型と異なる形式で取得できない?
int f1 = dbReader.GetInt32(0);
string f2 = dbReader.GetString(1);
string f3 = dbReader.GetString(2);
・・・
}
} while (dbReader.NextResult());
// DbDataReaderを使い終わったらCloseする
dbReader.Close();
となる。
挿入、更新、削除などのNonQuery処理
DbCommand.ExceuteNonQueryでSQL文の実行が行える。
// DbCommandに、実行したいSQL文をセット(サンプルなので適当なupdate文)
dbCmd.CommandText = "update foo set update='2007-01-02' where id=1";
// SQL文の実行
cmd.ExecuteNonQuery();
パラメータ化されたSQL文
java.sql.PreparedStatementの様に、SQL文中でパラメータを使用する事が可能となる。
// DbCommandに、実行したいSQL文をセット(サンプルなので適当なupdate文)
// パラメータマーカーは、DBに依存するそうだが、OLE DBでも、@idの様な記述で問題なく動く
dbCmd.CommandText = "update foo set update=@update where id=@id";
// パラメータの情報を設定
// DBに依存しないコードは記述できない???
// OLD DBの場合、パラメータを名前で判断していないので、
// パラメータの出現順とAddメソッドが同期していなければいけない
// また、実際のパラメータ名と異なる名前を使用してもエラーにならず、正しく処理される
dbCmd.Parameters.Add(new OleDbParameter("@update", OleDbType.Date));
dbCmd.Parameters.Add(new OleDbParameter("@id", OleDbType.Numeric));
// パラメータに値を設定
// dbCmd.Parameters[0].Value = '2007-01-02' としてもよいが、パラメータ名を使用して値を設定できる
dbCmd.Parameters["@update"].Value = '2007-01-02';
dbCmd.Parameters["@id"].Value = 1;
// SQL文の実行
cmd.ExecuteNonQuery();
ADO.NET 2.0でOLE DB、Oracle、SqlServerなど、個々のDBに依存したクラスを使用せずに、DbXxxxクラスでコードが記述できると言っているが、パラメータの型指定などでは、使用するDBに依存するコードとなってしまうらしい。
また、複数の行に対する処理の場合、
// DbCommandに、実行したいSQL文をセット(サンプルなので適当なupdate文)
dbCmd.CommandText = "update foo set update=@update where id=@id";
// パラメータの情報を設定
dbCmd.Parameters.Add(new OleDbParameter("@update", OleDbType.Date));
dbCmd.Parameters.Add(new OleDbParameter("@id", OleDbType.Numeric));
// パラメータに値を設定
dbCmd.Parameters["@update"].Value = '2007-01-02';
dbCmd.Parameters["@id"].Value = 1;
// SQL文の実行
cmd.ExecuteNonQuery();
// 次の処理のためのパラメータに値を設定
// 直前の処理と同じSQL文であれば、パラメータ情報を設定しなおす必要は無い
dbCmd.Parameters["@update"].Value = DateTime.Now;
dbCmd.Parameters["@id"].Value = 2;
// SQL文の実行
cmd.ExecuteNonQuery();
とすることも出来る
日付型のパラメータと定義してあれば、DataTime型のデータを設定しても大丈夫らしい。
DataGridView
.NET Framework version 2.0から、DataGridコントロールに代わるコントロールとして追加されたもので、DataSourceにデータオブジェクトを指定し、データの表示、編集作業を行う。
DataSourceには、
- Visual StudioのGUI操作で作成したデータソースオブジェクト
- DataSetオブジェクト
- DataTableオブジェクト
が使われる。
データの参照であれば、Visual StudioのGUI操作(データソース構成ウィザード)だけで大抵の処理が実現できてしまう。
Microsoftとしては、データの編集作業も含めて、Visual StudioのGUI操作だけで出来ると主張しているが、一般的なアプリケーションであれば、データの追加、修正作業を行う際には、データの内容、更新確認などの確認処理を経てから実際に更新処理を行うはずなのだが、これらのUI処理まで面倒見てはくれていない。
DataGridViewのDataSourceにDataSetを指定する際には、DataSet内に一つしかテーブルが存在しない状態であっても、「どのテーブルを表示させるのか」という指定が必要になる。
// DataGridViewに表示したいテーブル名
string sMemberName = "foo";
// 問い合わせ結果を受け取るための、DataSetクラスのインスタンスを作成する
DataSet ds = new DataSet();
// 問い合わせ結果を取得する
dbAdap.Fill(ds, sMemberName);
// 対象となるテーブルオブジェクトの存在確認
if (ds.Tables.IndexOf(sMemberName) >= 0)
{
// DataGridViewのDataSourceとDataSetを結びつける
dataGridView1.DataSource = ds;
// DataMemberプロパティが設定されていないと表示されない
dataGridView1.DataMember = sMemberName;
}
DataGridViewのDataSourceにDataTableを指定する場合は、
// DataGridViewに表示したいテーブル名
string sMemberName = "foo";
// 問い合わせ結果を受け取るための、DataSetクラスのインスタンスを作成する
DataSet ds = new DataSet();
// 問い合わせ結果を取得する
dbAdap.Fill(ds, sMemberName);
// 対象となるテーブルオブジェクトの存在確認
if (ds.Tables.IndexOf(sMemberName) >= 0)
{
// DataGridViewのDataSourceとDataTableを結びつける
dataGridView1.DataSource = ds.Tables[sMemberName];
}
となる。
さらに、DataAdapterのFillメソッドには、DataTableを指定できるので
// 問い合わせ結果を受け取るための、DataTableクラスのインスタンスを作成する
DataTable dt = new DataTable();
// 問い合わせ結果を取得する
dbAdap.Fill(dt);
// DataGridViewのDataSourceとDataTableを結びつける
dataGridView1.DataSource = dt;
と出来る。
データソース
Visual Studio プロジェクトのデータソースとは、アプリケーションから利用可能な
などのデータを指し、型指定されたDataSetとTableAdapterが用意される。
型指定されたDataSetを使用すると、
ds.Tables["foo"].rows[0]["FIELD1"];
と記述していたコードが
ds.foo[0].FIELD1;
となる。
フィールド値のNULLチェック(DBNull判定)は、「Is + 列名 + Null」形式のメソッドで判定する。
if ( !ds.foo[0].IsFIELD1Null() )
f1 = ds.foo[0].FIELD1;
コレクションのキー部分がプロパティ化されるわけだが、プロパティ化されたということは、単なるObject型データの連想配列として管理していた従来のDataSetと異なり、型が定義されているので、数値列に対して文字列を指定した様な場合、コンパイル時にエラーが出るのでコードの信頼性が高まる。
TableAdapterは、DataAdapterに型指定を追加したバージョン。
このTableAdapterオブジェクトには、挿入、削除、更新、問い合わせなどの機能をGUI操作により作成できる。
パラメータ付クエリは、「TableAdapter クエリの構成ウィザード」を使用して対話的に作成する事が出来、「クエリ ビルダ」ダイアログの「フィルタ」セルにパラメータを指定することで、=条件だけでなく、like条件なども定義できるが、パラメータ名については、使用するDBに依存したものになる。
MSDNのドキュメントを始め、大抵のサンプルでは、フィルタ名として「@param」などの、「@」を冠した名前が使われているが、これは、SQL Serverのパラメータマーカーで、OLE DBの場合は、「?」以外はパラメータとして認識されない。
DbCommandクラスでも、パラメータマーカーはDB依存度が低くなっているのに、TableAdapterでは、DB依存度が高い。
また、DataGridViewとデータソースを結び付けている場合、既定の処理では、FormLoadのイベント処理にデータソースからデータを取得するコードが自動的に追加されるのだが、データソースが存在していない場合、当然、例外が発生する。
自動的に追加されるコードは、try~catchで囲まれた形で追加されるわけでもなく、また、接続文字列変更や、再接続のためのサポートメソッドも見当たらない。
接続文字列プロパティは、読み取り専用なので直接XMLファイルを書き換えないと変更できないらしい。開発環境と、実稼働環境の接続設定が異なる場合に面倒な事になる。
まともなプロジェクトでは、開発時には実稼働環境を使うことはしないと思うので、この点の使い勝手は悪いとしか言いようが無いが、TableAdapterインスタンスのConnectionプロパティのConnectionStringを再設定するとうまく動いてくれるので、アプリケーションの最初で、接続文字列を設定するコードを記述しろという事か?
データベース作成
ADOX.CatalogClassを使用する事でデータベースを作成できるらしい。
ただし、データベースによっては作成できないものもあると思われる。
ADOX namespaceは、「Microsoft ADO Ext. 2.x for DDL and Security」に含まれているらしい。
ADOX.CatalogClassクラスのCreateメソッドの引数に、データベースへの接続文字列を指定することで作成する。
string sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data source=test.mdb";
ADOX.CatalogClass cat = new ADOX.CatalogClass();
cat.Create(sConnectionString );
XML
従来からInternet Explorerの一部として配布されていたMSXMLがあったが、.NET FrameworkのXMLクラスは、様々な点で使い勝手が良くなっている。
- C言語から使用する場合、文字列をBSTRなどの形にしないといけなかったが、C#言語の場合は、メモリ管理は自動だし、始めからUnicode対応なので、そのまま扱える
- 何も指定しなくても、改行やインデントされた状態でファイルへ出力できる
- ほぼCOMインターフェース直接アクセスに近いMSXMLと違い、自動でメモリ管理をしてくれるので、Nodeの参照カウンタ管理などを気にせずに使える
など
ところで、.NET FrameworkのXMLクラスは、ファイルの先頭がXML宣言(<?xml…)でないとエラーになるらしい。コメントであっても。
MS-XMLでは問題なかったのだが…。
また、二つのXMLオブジェクトの結合を行う場合、自身の子ノードでないノードをAppendChildなどで追加させる事になるのだが、自分がCreateしたノード以外のノードを追加する事が出来ないので、ImportNodeメソッドでコピーを作成した後にノードを追加する。
XmlDocument doc1 = new XmlDocument();
XmlDocument doc2 = new XmlDocument();
doc1.Load("test1.xml");
doc2.Load("test2.xml");
XmlNodeList lst = doc1.SelectNodes("/foo/*");
XmlNode parent = doc2.SelectSingleNode("/foo");
foreach (XmlNode node in lst)
{
parent.AppendChild(doc2.ImportNode(node, true));
}
LINQ
LINQ(Language Integrated Query: 統合言語クエリ)とは、.Net Framework 3.5から導入された、プログラム言語に統合されたクエリ機能。
一般的な問い合わせであれば、LINQだけで記述できるらしいので、複数のデータベースシステムへの対応が容易になる(可能性がある)。
また、プログラム言語の構文の一つと言う事から、表の列と、変数の比較処理も記述できる。
今までは、存在しない表、列を使用したSQL文を記述しても、実行時エラーが発生することはあっても、コンパイルエラーが発生することは無かった。
しかし、プログラム言語に統合される事により、存在しない表、列を指定した問い合わせ処理に対して、コンパイル時にエラーを検出する事が可能となる。
また、型指定されたDataSetと同様、列の型も管理しているので、数値列に対して文字列との比較、代入を行うような処理に対してもコンパイルエラーが発生する。
マイクロソフトのサイトでは、DataContextの作成は
// DataContext で接続文字列を取得します。
DataContext db = new DataContext("c:\\northwind\\northwnd.mdf");
と記述されているが、これは、SQL Serverでしか有効ではない。
つまり、AccessのMDBファイルを使用したいときなどは、この様な記述では例外が発生し正しく動作しない。
SQL Server以外のDBに対してDataContextを作成する場合は、IDbConnectionを引数にしたコンストラクタを使用することで可能となる。
// DbProviderFactoryのインスタンスを作成
DbProviderFactory dbProvider = DbProviderFactories.GetFactory ("System.Data.OleDb");
// DbProviderFactoryのインスタンスからDbConnectionを取得
DbConnection dbCon = dbProvider.CreateConnection();
// DbConnectionに、ConnectionStringを設定し、Openする
// DBファイルはtest.mdb(Microsoft Access MDB)
dbCon.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data source=test.mdb";
// DbConnectionのオブジェクトから、DataContextを作成する
DataContext db = new DataContext(dbCon);
※こんな面倒な事をしなくても、
// 接続文字列
string sCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data source=test.mdb";
// System.Data.OleDb.OleDbConnectionのインスタンスを作成
DbConnection dbCon = new System.Data.OleDb.OleDbConnection(sCon);
// DbConnectionのオブジェクトから、DataContextを作成する
DataContext db = new DataContext(dbCon);
で出来る。
LINQは、.Net Frameworkで扱えるオブジェクトに対する「汎用クエリ機能」なので、データベースやXML以外にも適用できる。
たとえば、以下の様なちょっと複雑なソート処理は
private static int Compare(int x, int y)
{
int result = (x % 3) - (y % 3);
if (result == 0)
{
result = y - x;
}
return result;
}
private void Test()
{
int[] numbers = { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
Array.Sort(numbers, Compare);
foreach (var i in numbers)
{
Console.Write("{0} ", i);
}
Console.WriteLine();
}
LINQを使うと
private void Test()
{
int[] numbers = { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
foreach (var i in numbers.OrderBy(i => i % 3).ThenByDescending(i => i))
{
Console.Write("{0} ", i);
}
Console.WriteLine();
}
と記述できる。
ただし、LINQはあくまでも「クエリ」処理を行うものなので、Array.Sortメソッドの様に、データソース自体を変更する事は無い。
上記の処理で、データソースを変更したい場合は、
numbers = numbers.OrderBy(i => i % 3).ThenByDescending(i => i).ToArray();
と並べ替えメソッドの最後に、ToArray()をつけ、その時点での問い合わせ結果を配列オブジェクトとして生成する。
また、
private void Test()
{
int[] numbers = { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
// 配列全体の平均値
Console.WriteLine(numbers.Average());
// 配列の要素数 (numbers.Lengthと同じ)
Console.WriteLine(numbers.Count());
// 配列内の5以下の数値の平均値
Console.WriteLine(numbers.Where(i=>(i<=5)).Average());
// 配列内の偶数値の要素数
Console.WriteLine(numbers.Where(i => (i<=5)).Count());
}
の様な事も出来る。
ファイル名検索
System.IO.DirectoryクラスのGetFilesメソッドを使うとファイル名を手軽に指定したディレクトリ下のファイルをフルパスで文字列配列として取得できる。
string[] sFiles = Directory.GetFiles(@"c:\temp");
とすると、c:\tempディレクトリ下のファイル名を取得できる。
string[] sFiles = Directory.GetFiles(@"c:\temp", "*.txt");
とすると、c:\tempディレクトリ下の、拡張子がtxtのファイル名を取得できる。
ただし、上記の方法では、サブディレクトリ下を検索していない。
そこで、サブディレクトリも検索対象とする場合は、SearchOptionを指定し、
string[] sFiles = Directory.GetFiles(@"c:\temp", "*.txt", SearchOption.AllDirectories);
とする事で、c:\temp以下の全てのディレクトリ下にある、拡張子がtxtのファイルを取得できる。
なお、無効なディレクトリを指定した場合は、例外が発生する。
ファイルやディレクトリの存在確認
System.IO.FileクラスのExistsメソッドを使うとファイルの存在確認が手軽に実現できる。
if (File.Exists(@"c:\temp\foo.txt"))
{
// ファイルが存在する
}
また、System.IO.DirectoryクラスのExistsメソッドを使えば、ディレクトリの存在確認を行える。
Drag & Drop
Drag & Dropを実現するためには
- フォームのAllowDropプロパティをtrueにする
- Drag & Dropの準備を行うDragEnterイベントを作成する
- Drag & Dropされたファイル名などを受け取るDragDropイベントを作成する
を行う必要がある。
Drag & Dropで複数のファイルを受け取る処理の場合、
private void Form1_DragEnter(object sender, DragEventArgs e)
{
// ファイルをDrag & Dropの処理対象にする場合
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.All;
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] sFiles = (string[])e.Data.GetData(DataFormats.FileDrop);
Array.Sort(sFiles); // ファイル名を並べ替えたいときには必要
foreach (string sFileName in sFiles)
Console.WriteLine(sFileName);
}
}
の様になる。
コレクション
コレクションの中身を順次処理する場合、
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("Jan", "睦月");
dic.Add("Feb", "如月");
…
foreach (string sKey in dic.Keys)
{
Console.WriteLine(dic[sKey]);
}
の様に、foreachを使用する事が多いと思うが、foreach中でコレクションを処理しながら、削除処理を行う
foreach (string sKey in dic.Keys)
{
Console.WriteLine(dic[sKey]);
dic.Remove(sKey);
}
の様な処理を実行すると、コレクションが変更されたと言う事で、InvalidOperationExceptionの例外が発生する。
この様なときには、
string[] sKeys = new string[dic.Count];
dic.Keys.CopyTo(sKeys, 0);
foreach (string sKey in sKeys)
{
Console.WriteLine(dic[sKey]);
dic.Remove(sKey);
}
と、キー(dic.Keys)の値をCopyToメソッドを使用して一旦取り出して処理をすると良い?
最終更新:2008年07月25日 20:18