QA@IT

C#でdatatableを使用し、datatableの中で特定のフィールドを検索するプログラムを作ろうと考えています。

9517 PV
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;

namespace ConsoleApplication17
{
    class Program
    {
        static void Main(string[] args)
        {          
            //SQL Serverからdatatableへデータをコピーしています。
            string sConnectionString;
            sConnectionString = @"Data Source=AAA\AAA;Initial Catalog=NAME;User ID=sa;Password=0000";
            SqlConnection objConn = new SqlConnection(sConnectionString);
            objConn.Open();
            SqlDataAdapter daAuthors = new SqlDataAdapter("Select * From Kondate", objConn);
            DataSet dsPubs = new DataSet("Pubs");
            daAuthors.FillSchema(dsPubs, SchemaType.Source, "Kondate");
            daAuthors.Fill(dsPubs, "Kondate");
            DataTable tblAuthors;
            tblAuthors = dsPubs.Tables["Kondate"];     
            string sConnectionString2;
            sConnectionString2 = @"Data Source=AAA\AAA;Initial Catalog=NAME;User ID=sa;Password=0000";
            SqlConnection objConn2 = new SqlConnection(sConnectionString2);
            objConn2.Open();

            SqlDataAdapter daAuthors2 = new SqlDataAdapter("Select * From Kondate_Syousai", objConn2);

            DataSet dsPubs2 = new DataSet("Pubs");

            daAuthors2.FillSchema(dsPubs, SchemaType.Source, "Kondate_Syousa");
            daAuthors2.Fill(dsPubs, "Kondate_Syousa");

            DataTable tblAuthors2;
            tblAuthors2 = dsPubs.Tables["Kondate_Syousa"];

            foreach (DataRow drCurrent in tblAuthors.Rows)
            {

                if (drCurrent["Code"].ToString() == "")
                {
          //rNoShisetuには00001が入っています。
                    string rNoShisetu= drCurrent["NoShisetu"].ToString();
          //rFoodCodeには10000が入っています。
                    string rFoodCode = drCurrent["FoodCode"].ToString();
                    //ここでrowsにNoShisetuの値とFoodCodeの値が合致するものを入れます。
                   DataRow[] rows = tblAuthors2.Select("NoShisetu= rNoShisetu and FoodCode = rFoodCode");

                    MessageBox.Show("終了しました。");

                }
            }
            Console.ReadLine();
        }
    }
}

特定のdatatableの値を検索し取り出すのに

 DataRow[] rows = tblAuthors2.Select("NoShisetu= rNoShisetu and FoodCode = rFoodCode");

という条件式でできるのでしょうか。
特にrNoShiseturFoodCodeは変数で値を取り出すにはこのような形でよろしいでしょうか。よろしくおねがいします。
visual Studio 2010を使用しています。

回答

今回の場合、以下の様にすればと実行はされると思います。

    //ここでrowsにNoShisetuの値とFoodCodeの値が合致するものを入れます。
    DataRow[] rows = tblAuthors2.Select("NoShisetu= '" + rNoShisetu + "' and FoodCode = '" + rFoodCode + "'");

元のままですと、「rNoShisetu」や「rFoodCode」という列をDataTable上から探そうとしてしまいます。値は自動的に展開されません。


usingなどを利用してDB接続が確実に閉じられるようなコードにした方が良いでしょう。
例としては 以下のようなコードになります。

static void Main(string[] args) {

    // 同じ接続文字列なら定数などにして使いまわしましょう
    const string sConnectionString = @"Data Source=AAA\AAA;Initial Catalog=NAME;User ID=sa;Password=0000";


    //SQL Serverからdatatableへデータをコピーしています。

    DataTable tblAuthors;
    using (SqlConnection objConn = new SqlConnection(sConnectionString)) {
        objConn.Open();
        using (SqlDataAdapter daAuthors = new SqlDataAdapter("Select * From Kondate", objConn)) {
            DataSet dsPubs = new DataSet("Pubs");
            daAuthors.FillSchema(dsPubs, SchemaType.Source, "Kondate");
            daAuthors.Fill(dsPubs, "Kondate");
            tblAuthors = dsPubs.Tables["Kondate"];
        }
    }


    // 以下は varを積極的に利用した例です。
    var tblAuthors2 = null as DataTable;
    using (var objConn2 = new SqlConnection(sConnectionString)) {
        objConn2.Open();
        using (var daAuthors2 = new SqlDataAdapter("Select * From Kondate_Syousai", objConn2)) {
            var dsPubs2 = new DataSet("Pubs");

            // dsPubsとなっていましたが、dsPubs2 だと思います。
            // dsPubを使いまわしてもいいでしょうけれど。
            daAuthors2.FillSchema(dsPubs2, SchemaType.Source, "Kondate_Syousa");    
            daAuthors2.Fill(dsPubs2, "Kondate_Syousa");
            tblAuthors2 = dsPubs2.Tables["Kondate_Syousa"];

            // 上の3行の代わりに、下の3行で DataSetを作らず、DataTableに対して実行するという手もあります。
            // tblAuthors2 = new DataTable("Kondate_Syousa");
            // daAuthors2.FillSchema(tblAuthors2, SchemaType.Source);
            // daAuthors2.Fill(tblAuthors2);
        }
    }


    foreach (DataRow drCurrent in tblAuthors.Rows) {

        if (drCurrent["Code"].ToString() == "") {
            //rNoShisetuには00001が入っています。
            string rNoShisetu = drCurrent["NoShisetu"].ToString();
            //rFoodCodeには10000が入っています。
            string rFoodCode = drCurrent["FoodCode"].ToString();
            //ここでrowsにNoShisetuの値とFoodCodeの値が合致するものを入れます。
            DataRow[] rows = tblAuthors2.Select("NoShisetu= '" + rNoShisetu + "' and FoodCode = '" + rFoodCode + "'");

            MessageBox.Show("終了しました。");
        }
    }
    Console.ReadLine();
}

あとは絞り方として( tblAuthors2.Select の代わりとして )LINQを使うという手段もありますので、興味があれば調べられるとよいでしょう。

その他、DBからSelectする段階で結合してしまうという手もあります。
SQLで条件を指定する場合は、上記のような単純な文字結合ではなくパラメータを使用するようにしてください。
(上記のコードでも、rNoShisetuや rFoodCodeの文字の中に シングルクォート ' が含まれているとおかしなことになります。)


少し時間が空いてしまいましたが、コメント欄でDataAdapterのConnection関連の話が出たので余談として補足しておきます。

DbDataAdapter (SqlDataAdapterの親クラス)はFillやFillSchemaが呼ばれると紐づけられているConnectionの状態に応じて以下の様に振る舞いを変えます。

  • Fillメソッド呼び出し時 Connectionが Openしていない場合 Openして Fillの処理後で 『Closeされます』。
  • Fillメソッド呼び出し時 Connectionが Openしている場合はそのまま使用して、Fill後もそのまま『閉じられません』。

内部的には QuietOpen/QuietClose で元の状態見て開けたり開けなかったり、閉じたり閉じなかったりしているだけです。
厳密にいうといろいろありますがややこしくなるだけなので、DbDataAdapterおよびSqlDataAdapterでは上記の様になります。

以下のページの 「例」の下のメモのコメントも参考になるでしょう。

http://msdn.microsoft.com/ja-jp/library/bh8kx08z(v=vs.110).aspx

Fill メソッドは、接続がまだ開いていないことを認識すると DataAdapter が使用している Connection を暗黙的に開きます。 Fill が接続を開いた場合は、Fill の終了時に Fill が接続を終了します。
これにより、Fill や Update などの単一の操作を扱う場合にコードを簡略化できます。

とあります。単一操作なら楽できますよという事ですね。
一方で

これに対し、開いている接続を必要とする複数の操作を実行する場合は、Connection の Open メソッドを明示的に呼び出し、データ ソースに対する操作の実行後に Connection の Close メソッドを呼び出すことでアプリケーションのパフォーマンスを改善できます。

とも書いてあります。
FillSchemaとFillを連続して行っている今回の場合はこちらが該当しますかね。
パフォーマンス的には Fill使ってる時点で無視できるオーダーと思いますが、 間をあけず 一連の処理として連続でDBアクセスするとわかっていたら閉じるのはあまり得作ではないですし、開けておく場合が多いんじゃないでしょうかね。

ここでのポイントは、接続が開いているか開いていないかで振る舞いが異なる事です。
処理の組み立てというものがありますので絶対どちらがいいというのはありませんが、違いがあることは覚えておいた方がいいでしょう。

以上、余談でした。

編集 履歴 (2)
  • ご回答ありがとうございます。うまくいきました。ご親切にありがとうございます。 -
  • SqlDataAdapter を使うのであれば、特に理由がない限り、Open/Close は SqlDataAdapter に任せたほうが良いのではないでしょうか? -
  • Openを自前で行っていることについての指摘でしょうか?正直今回のコードでそこまで気にしませんでしたが、これであれば任せてもいいかも知れませんね。
    もう少し整理すると 一つ目の接続と二つ目の接続は共有できそうですのでConnectionのusingを1つにしてOpenしたまま2つ目のSqlDataAdapterに渡してもいいと思います。
    -
  • お手本として書かれたのだと思いますが、そうであれば、SqlDataAdapter.Fill メソッドを使う場合、自動的に接続が Open され、データを取得した後で自動的に Close されるということを書かれた方がよさそうだと思いました。 -

DataRow[] rows = tblAuthors2.Select("NoShisetu= rNoShisetu and FoodCode = rFoodCode");
という条件式でできるのでしょうか。

実際にやってみたんでしょうか?

やってないなら、実際にやってみてその結果を書いていただけませんか? すでにやってみて期待通りにならないということなら、どういう結果になったかなど詳しく書いていただけませんか?

#今回の質問とは関係ない話ですが、非常に重要なこととして、接続を Open したら必ず Close するようにしましょう。具体的な方法は以下のページを読んでください。

.NETの例外処理 Part.2
http://blogs.msdn.com/b/nakama/archive/2009/01/02/net-part-2.aspx

編集 履歴 (0)
  • このプログラムを実行すると、{"列 [rShisetsuCode] が見つかりません。"}とでてきます。 -
  • 情報を小出しにしないで、最初からご自分の知っている限りの情報を書きましょう。どの行でエラーが出るんですか? エラーメッセージは省略しないで書けませんか? rShisetsuCode って何ですか?(アップされたコードのどこにも見当たりませんが) -
  • あと、ご自分の環境(OS, .NET, SQL Server のバージョンなど)も書いてください。 -
ウォッチ

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