「.NET固有の機能」の編集履歴(バックアップ)一覧に戻る

.NET固有の機能 - (2007/07/11 (水) 17:50:58) の編集履歴(バックアップ)


.NET Framework固有のTipsです。


アプリケーションの設定値


アプリケーション/ユーザごとの設定を簡単に扱える。
マイクロソフト情報(C# で設定を使用する)

要約

  • 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で検索したほうが手軽な気がする。

使用するクラス

  • DbProviderFactories
システムに登録されているDbProviderFactoryインスタンスを管理しているクラス
  • DbProviderFactory
プロバイダ情報を格納し、下記クラスのインスタンスを作成するためのクラス
JDBCのjava.sql.DriverManagerみないなもの??
  • DbConnection
データベースへの接続状態を表すクラス
DbProviderFactoryから取得する
JDBCのjava.sql.Connectionに相当する
  • DbCommand
DBに対して実行する命令(SQL文など)を表すクラス
DbProviderFactoryから取得する
JDBCのjava.sql.PreparedStatementに相当する
  • DbDataReader
DBに対する問い合わせ結果を表すクラス
DbCommand.ExceuteQueryから取得する
JDBCのjava.sql.ResultSetに相当する
  • DbDataAdapter
DBに対する命令、接続、結果データの格納を表すクラス
DbCommandとDataSetの橋渡し役
  • DataSet
メモリ上に展開された、データを階層的に管理するクラス
DBの問い合わせ結果などのデータを格納(データはDbDataAdapterから得られる)し、問い合わせ処理に問題が無ければ、1つ以上のDataTableクラスオブジェクトを持つ事になる
なお、問い合わせ結果が1万行あれば、1万行分のデータをメモリに展開する
  • DataTable
一つのテーブルに対するデータを格納するクラス
  • DataColumn
列情報を格納するクラス
  • DataColumnCollection
テーブル内の全ての列情報を格納するクラス
DataColumnのコレクション
  • DataRow
行情報を格納するクラス
  • DataRowCollection
テーブル内の全ての行情報を格納するクラス
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とDataSetを結びつける
    dataGridView1.DataSource = ds.Tables[sMemberName];
}
となる。


データソース


Visual Studio プロジェクトのデータソースとは、アプリケーションから利用可能な
  • データベース
  • オブジェクト
  • Webサービス
などのデータを指し、型指定されたDataSetとTableAdapterが用意される。

型指定されたDataSetを使用すると、
ds.Tables["foo"].rows[0]["FIELD1"];
と記述していたコードが
ds.Tables.foo[0].FIELD1;
となる。
コレクションのキー部分がプロパティ化されるわけだが、プロパティ化されたということは、型も定義された事になるので、数値列に対して文字列を指定した様な場合、コンパイル時にエラーが出るのでコードの信頼性が高まる。

TableAdapterは、DataAdapterに型指定を追加したバージョン。
このTableAdapterオブジェクトには、挿入、削除、更新、問い合わせなどの機能をGUI操作により作成できる。
パラメータ付クエリは、「TableAdapter クエリの構成ウィザード」を使用して対話的に作成する事が出来、「クエリ ビルダ」ダイアログの「フィルタ」セルにパラメータを指定することで、=条件だけでなく、like条件なども定義できるが、パラメータ名については、使用するDBに依存したものになる。
MSDNのドキュメントを始め、大抵のサンプルでは、フィルタ名として「@param」などの、「@」を冠した名前が使われているが、これは、SQL Serverのパラメータマーカーで、OLE DBの場合は、「?」以外はパラメータとして認識されない。
DbCommandクラスでも、パラメータマーカーはDB依存度が低くなっているのに、TableAdapterでは、DB依存度が高い。



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));
}


ファイル名検索

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);
    }
}
の様になる。