Archive

Archive for 2007年9月

LINQ to SQL:データの追加

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

データベースにデータを追加してみます。
次のようなプログラムを記述します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LINQ5
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            People newPerson = new People
            {
                Name = "オノ シュウジ",
                Gender = 1,
                Age = 41
            };

            dtc.People.Add(newPerson);

            Console.WriteLine("id={0}, 名前={1}, 性別={2}, 年齢={3}",
                newPerson.id, newPerson.Name, newPerson.Gender, newPerson.Age);

            dtc.SubmitChanges();

            Console.WriteLine("id={0}, 名前={1}, 性別={2}, 年齢={3}",
                newPerson.id, newPerson.Name, newPerson.Gender, newPerson.Age);
            Console.Read();
        }
    }
}

追加する人のインスタンス(newPerson)を作成し、テーブル(dtc.People)に追加しています。
実行結果はこんな感じになります。

 image

当然データも追加されています。

 image_3

上記のプログラムからわかることは、dtc.SubmitChanges(); を呼び出したときに実際のデータの追加が行われるということ、そして、データを追加したあとに自動的に追加したデータのidを取り込んで(SELECT文の発行)インスタンスの内容が更新(idが0から11に変更)されているということです。

データベース上のid項目にはIdentity属性を設定しているため、データの追加後でないとこの値は確定しないわけですが、そのあたりまでLINQ to SQLが面倒をみてくれているんですね。

カテゴリー:.NET, LINQを楽しむ

LINQ to SQL:検索条件を指定する

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

データの検索時にその場でいくつかの条件を指定したい、という場合があります。
あるときは成年の男性を取り出したい、また別のときは未成年で名前に「コ」の付く人を取り出したい、こんな場合にどのようにLINQを記述すればよいか考えてみましょう。

条件を適宜入力できるようにするのが良いのでしょうが、ちょっとサンプルが見づらくなるので、ここでは変数に条件を設定する形で条件の指定を行います。

まずは年齢について考えてみます。
条件として数字が与えられていたらその数字以上の年齢の人を抽出し、条件が与えられていない場合はすべての人を抽出する、というプログラムを考えてみました。

using System;
using System.Linq;

namespace LINQ4
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            int? ageCheck = null;

            var query = from p in dtc.People
                        where p.Age >= (ageCheck ?? 0)
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

年齢の条件を設定する変数としてnullableなint型の変数ageCheckを利用しています。
上記のプログラムでは、ageCheckに値が設定されていません(nullを設定)。この場合 ?? 演算子の働きによって0がp.Ageとの比較対象になります。
この結果、すべての人が抽出されます。

 image

上記のプログラムを修正し、"int? ageCheck = 30;" とすると、30歳以上の人が抽出されます。

 image_3

年齢の場合、条件の指定がなければ0歳という特定の値と比較させることができますが、性別の場合はどうするのが良いのでしょうか?
この場合、条件の指定がなければ、比較しようとしているデータの値そのものと比較を行うようにプログラムを記述します。このように自分自身の値と比較するという方法はストアドプロシージャー内でよく使われたりするようです。

using System;
using System.Linq;

namespace LINQ4
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            int? ageCheck = null;
            int? genderCheck = 1;

            var query = from p in dtc.People
                        where p.Age >= (ageCheck ?? 0) && p.Gender == (genderCheck ?? p.Gender)
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

抽出条件をつなぐ場合、"AND" ではなく && 演算子を利用します。
ここではまずは男性(genderCheck=1)を抽出してみました。

 image_4

ここで ?? 演算子はCOALESCEというSQL文に変換されています。同じ ?? 演算子でも状況によって異なるSQL文が生成されるというのは興味深いところです。
genderCheckにnullを設定すると、ちゃんと全員が抽出されることを確認しておきましょう。

 image_5

SQL文に渡されている@p1の値が空になり、全員が抽出されていることが確認できます。

最後に名前の部分一致検索の方法を考えてみます。
まずはString.Containsを使って次のように記述してみたのですが、、、

    int? ageCheck = null;
    int? genderCheck = null;
    string namePart = null;

    var query = from p in dtc.People
                where p.Age >= (ageCheck ?? 0)
                      && p.Gender == (genderCheck ?? p.Gender)
                      && p.Name.Contains(namePart ?? p.Name)
                select p;

これではqueryを評価する時点で「String.Contains メソッドに対しては、クライアント上で評価できる引数だけがサポートされます。」というエラーになってしまいます。
そこで、SqlMethods.Likeを使ってみました。

using System;
using System.Linq;
using System.Data.Linq.SqlClient;

namespace LINQ4
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            int? ageCheck = null;
            int? genderCheck = null;
            string namePart = null;

            var query = from p in dtc.People
                        where p.Age >= (ageCheck ?? 0)
                              && p.Gender == (genderCheck ?? p.Gender)
                              && SqlMethods.Like(p.Name, "%" + (namePart ?? p.Name) + "%")
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

string型に対しても ?? 演算子は有効です。
上記のプログラムでは結果として全員が抽出されます。

 image_6

namePart変数に"タカ"という文字列を設定すると、名前の一部に"タカ"を含む人だけが抽出されます。

 image_7

これで年齢、性別、名前に対して指定した条件を組み合わせてデータを抽出するLINQを完成することができました。
実際にいろいろな条件を設定して動作を試してみてください。

#ある意味、パラメータを利用したSQLクエリの書き方の勉強にもなっているような気がする。。。

カテゴリー:.NET, LINQを楽しむ

LINQ to SQL:文字列検索のための2つの方法

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

前の投稿ではデータの抽出条件として数字の一致と大小比較を利用しています。
抽出条件で数字ではなく文字列を利用したい場合はどのようにすればよいか調べてみます。

条件として、名前の中に"タカ"という文字列がある人を抽出してみましょう。
String型が持っているメソッドを眺めていくと、Containsメソッドがあります。
これを使ってプログラムを記述してみます。

using System;
using System.Linq;

namespace LINQ4
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            var query = from p in dtc.People
                        where p.Name.Contains("タカ")
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

この実行結果は次のようになります。

 image

ContainsメソッドはLIKEに変換され、またContainsメソッドに渡した引数("タカ")の前後に"%"が追加されています。
このようなSQL文が生成されることで、欲しかった結果を正しく取り出すことができています。
String型には部分一致に利用するContainsメソッド以外にStartsWith、EndsWithメソッドがありますので、
これらを使って前方一致や後方一致の検索を行うことが可能です。

これでとりあえず文字列の検索はできましたが、LINQはSQL文に似た構文を持っているのにLIKEは
持っていないのでしょうか?

調べてみたところ、次のような方法があるようです。

            var query = from p in dtc.People
                        where System.Data.Linq.SqlClient.SqlMethods.Like(p.Name, "%タカ%")
                        select p;

System.Data.Link.SqlClient名前空間にSqlMethodsというクラスが用意されており、このクラスが
Likeメソッドを持っています。
このプログラムの実行結果は次のとおり。

 image_3

String.Containsを利用した場合と、生成されるSQL文もその結果も変わっていないことが確認できます。

このときちょっと注意する必要がある点として、String.Containsの場合はSQL文に渡される文字列の
前後の"%"は自動的に追加されますが、SqlMethods.Likeの場合は渡す文字列自体に"%"を
付加しておかなければいけない、という点があります。
つまり、Stringでは前方一致や後方一致検索ではStartsWithやEndsWithといったようにメソッド
そのものを使い分けますが、SqlMethods.Likeではメソッドはひとつで渡す文字列によって前方一致
だったり後方一致だったり部分一致だったりする、ということです。

生成されるSQL文は同じですから、通常はString.Contains等を使っていればいいように思います。
Likeを使わないといけない例を見つけたのですが、それは別の記事で。

カテゴリー:.NET, LINQを楽しむ

LINQ to SQLで抽出と並び替え

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

LINQ to SQLではSQL Server上のデータベースに含まれるデータをプログラムから操作することができます。
まずは以下のような内容でPeopleテーブルを作成しました。

 image

次にプロジェクトに新しい項目の追加からLINQ to SQLクラスを追加します。

 image_3

新たに表示されるページ(LINQTEST.dbml)の左ペインにサーバエクスプローラからPeopleテーブルをドラッグ アンドドロップすると、Peopleクラスが自動的に生成されます。

 image_4

このページを保存して、自動的に生成されたクラスを確定したうえで、メインのプログラムを記述します。

using System;
using System.Linq;

namespace LINQ4
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQTESTDataContext dtc = new LINQTESTDataContext();
            dtc.Log = Console.Out;

            var query = from p in dtc.People
                        where p.Gender == 1 && p.Age >= 20
                        orderby p.Age
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

LINQ to SQLで重要になるのは、テーブルをドラッグ アンド ドロップすることで自動的に生成されるDataContextです。
ドラッグ アンド ドロップによりどのようなプログラムコードが自動生成されているかは、LINQ to SQL クラス名.designer.cs ファイルを見ることで確認できます。
このDataContext(上記ではLINQTESTDataContext)に含まれるテーブル(dtc.People)に対してLINQを記述していきます。

LINQ自体の記述はObjectやXMLを対象とした場合とほとんど大差はありません。
拡張メソッドを利用した記述も同じように利用できます。DataContextに含まれるテーブルが操作の対象となる点にだけ気をつけていれば十分です。

            var query = dtc.People.Where(p => p.Gender == 1 && p.Age >= 20).OrderBy(p => p.Age);

実行結果は以下のようになります。

 image_5

この実行結果にSQL文が含まれるのはプログラム中でDataContextのLogプロパティに標準出力(Console.Out)を設定しているためです。
LINQ to SQLのプログラムがパラメータを利用したSQLクエリに変換され、実行されていることがわかります。

このSQLクエリがどのタイミングで実行されるのか、ブレークポイントを指定してデバックを実行して確認してみましょう。

 image_6

上記のように、LINQの記述の後のforeachにブレークポイントを設定した場合、この時点ではコンソール上にSQL文は表示されません。
F11キーを押して1ステップずつ確認していくと、2ステップ目の以下のタイミングでSQL文がコンソール上に表示されます。

 image_7

 image_8

このことにより、LINQの記述でquery変数に直接データが設定されるのではなく、query変数の内容にアクセスしようとしたタイミング(foreach内部)でSQL文が生成/発行され、データが取り出されることが確認できます。

カテゴリー:.NET, LINQを楽しむ

LINQ to XMLで抽出と並び替え

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

LINQ to XMLということで、LINQはXML形式のデータを扱うこともできます。
たとえば次のようなプログラムで。

using System;
using System.Linq;
using System.Xml.Linq;

namespace LINQ3
{
    class Program
    {
        static void Main(string[] args)
        {
            XElement People = XElement.Parse(
                @"<People>
                <Person>
                    <Name>オサダ トシヒロ</Name>
                    <Gender>1</Gender>
                    <Age>32</Age>
                </Person>
                <Person>
                    <Name>カネフジ タカエ</Name>
                    <Gender>2</Gender>
                    <Age>46</Age>
                </Person>
                <Person>
                    <Name>キョウゴク トシツグ</Name>
                    <Gender>1</Gender>
                    <Age>11</Age>
                </Person>
                <Person>
                    <Name>コウダ キミタカ</Name>
                    <Gender>1</Gender>
                    <Age>17</Age>
                </Person>
                <Person>
                    <Name>シモイズミ エイコ</Name>
                    <Gender>2</Gender>
                    <Age>13</Age>
                </Person>
                <Person>
                    <Name>センザイ シュウイチロウ</Name>
                    <Gender>1</Gender>
                    <Age>43</Age>
                </Person>
                <Person>
                    <Name>ソウリョウ ノリカズ</Name>
                    <Gender>1</Gender>
                    <Age>21</Age>
                </Person>
                <Person>
                    <Name>マツバネ タツコ</Name>
                    <Gender>2</Gender>
                    <Age>24</Age>
                </Person>
                <Person>
                    <Name>ミツジマ タカコ</Name>
                    <Gender>2</Gender>
                    <Age>32</Age>
                </Person>
                <Person>
                    <Name>ヤハタ トシチカ</Name>
                    <Gender>1</Gender>
                    <Age>28</Age>
                </Person>
             </People>");

            var query = from p in People.Descendants("Person")
                        where int.Parse(p.Element("Gender").Value) == 1 && int.Parse(p.Element("Age").Value) >= 20
                        orderby int.Parse(p.Element("Age").Value)
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Element("Name").Value, item.Element("Age").Value);
            }

            Console.Read();
        }
    }
}

LINQでXMLを扱うためにはいくつか注意しないといけないところがあります。

  • "using System.Xml.Linq; "という記述を追加しておきましょう。
  • XMLデータはXElementクラスに格納します。
  • LINQの対象となるのは、XElementからDescendantsメソッドで取り出したデータです。
  • 各Elementの値(Value)は文字列なので、適切な型にキャストしましょう。

LINQプログラムの基本的な部分はObjectを対象としたときとXMLを対象としたときで変化はありません。
当然、次のように拡張メソッド形式での記述も利用できます。

               var query = People.Descendants("Person")
                              .Where(p => int.Parse(p.Element("Gender").Value) == 1 && int.Parse(p.Element("Age").Value) >= 20)
                              .OrderBy(p => int.Parse(p.Element("Age").Value));

実行結果もまったく同じです。

#実行結果が同じになるようにプログラム組んでるんだから当然ですけどね。

image

カテゴリー:.NET, LINQを楽しむ

LINQ to Objectで抽出と並び替え(配列編)

※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

LINQ to ObjectはListだけを対象にしているわけではありません。
配列もその対象となります。 ということで、ソースコード。

using System;
using System.Linq;

namespace LINQ2
{
    class Program
    {
        static void Main(string[] args)
        {
            var People = new[]{
               new { Name="オサダ トシヒロ", Gender=1, Age=32},
               new { Name="カネフジ タカエ", Gender=2, Age=46},
               new { Name="キョウゴク トシツグ", Gender=1, Age=11},
               new { Name="コウダ キミタカ", Gender=1, Age=17},
               new { Name="シモイズミ エイコ", Gender=2, Age=13},
               new { Name="センザイ シュウイチロウ", Gender=1, Age=43},
               new { Name="ソウリョウ ノリカズ", Gender=1, Age=21},
               new { Name="マツバネ タツコ", Gender=2, Age=24},
               new { Name="ミツジマ タカコ", Gender=2, Age=32},
               new { Name="ヤハタ トシチカ", Gender=1, Age=28}
            };

            var query = from p in People
                        where p.Gender == 1 && p.Age >= 20
                        orderby p.Age
                        select p;

            foreach (var item in query)
            {
                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);
            }

            Console.Read();
        }
    }
}

なんかデータ作ってるところがすっきりして見えるのは、気のせいではなく、匿名クラスを使っているからです。
まぁ、サンプルだから使える手法ですかね。

LINQの部分はデータがListのときとまったく同じです。
当然、拡張メソッドによる実装も同じく使えます。

            var query = People.Where(p => p.Gender == 1 && p.Age >= 20).OrderBy(p => p.Age);

実行結果も同じになります。

image

カテゴリー:.NET, LINQを楽しむ

LINQ to Objectで抽出と並び替え(List<T>編)


※この投稿はMicrosoft Visual Studio 2008 Beta2で動作を確認しています。

やっと最近LINQに触り始めて、今一番楽しいところなんで、自分がやってみたことをまとめていきたいと思います。

まぁ、まだまだこんなことができるんだぁ、と文法を追っているレベルなんで、たいしたことは書けないですが。

ということで、まずはListに格納されているオブジェクトを操作してみます。

何はともあれソースから。

using System;

using System.Collections.Generic;

using System.Linq;

namespace LINQ1

{

    class Person

    {

        public string Name { get; set; }

        public int Gender { get; set; }

        public int Age { get; set; }

    }

    class Program

    {

        static void Main(string[] args)

        {

            List<Person> People = new List<Person>{

               new Person{ Name="オサダ トシヒロ", Gender=1, Age=32},

               new Person{ Name="カネフジ タカエ", Gender=2, Age=46},

               new Person{ Name="キョウゴク トシツグ", Gender=1, Age=11},

               new Person{ Name="コウダ キミタカ", Gender=1, Age=17},

               new Person{ Name="シモイズミ エイコ", Gender=2, Age=13},

               new Person{ Name="センザイ シュウイチロウ", Gender=1, Age=43},

               new Person{ Name="ソウリョウ ノリカズ", Gender=1, Age=21},

               new Person{ Name="マツバネ タツコ", Gender=2, Age=24},

               new Person{ Name="ミツジマ タカコ", Gender=2, Age=32},

               new Person{ Name="ヤハタ トシチカ", Gender=1, Age=28}

            };

            var query = from p in People

                        where p.Gender == 1 && p.Age >= 20

                        orderby p.Age

                        select p;

            foreach (var item in query)

            {

                Console.WriteLine("名前={0}, 年齢={1}", item.Name, item.Age);

            }

            Console.Read();

        }

    }

}

上記のプログラムでListに格納しているのは名前、性別、年齢というデータを持つ

Personクラスのオブジェクトです。

このオブジェクトの集合の中から、成人(Age>=20)の男性(Gender=1)を取得し、

年齢の順に並べてみています。

結果は次のようになります。

 image

このような結果を得るために、LINQが必ずしも必要なわけではありません。

たとえばList内のデータをひとつずつ取り出して条件に合致するデータだけを残し、

そのデータを並び替えるプログラムを書くことはそれほど難しくないはずです。

ただし、LINQを使うと下記のようにSQL文を書いたことがある人ならすぐに理解できる

簡潔な記述でデータの抽出と並び替えを一度に行うことができます。

            var query = from p in People

                        where p.Gender == 1 && p.Age >= 20

                        orderby p.Age

                        select p;

ちなみに、上記のプログラムは以下のように書くこともできます。

var query = People.Where(p => p.Gender == 1 && p.Age >= 20).OrderBy(p => p.Age);

このWhere()やOrderBy()は拡張メソッドとして実装されています。

実はこの拡張メソッドによる実装がLINQの基礎であって、SQL文のような記述は、コンパイル時に

拡張メソッドを使った方法に置き換えられて実行される、と考えてよいと思います。

カテゴリー:.NET, LINQを楽しむ