2015年09月28日

EntityFramework(6):リレーションシップ その1

   このエントリーをはてなブックマークに追加 Clip to Evernote
NorthwindデータベースのCategoryテーブルとProductsテーブルは、1対多の関係があります。
この場合のデータ参照について見てみます。

Category.csを見てみると、Categoryクラスには、

  public virtual ICollection Products { get; set; }

というプロパティが存在します。 一方、Productクラスには、

  public virtual Category Category { get; set; }

というプロパティが存在します。
ここから、 「一つの製品カテゴリーには、複数の製品が関連付けされている」 「一つの製品には、一つのカテゴリが関連付けされている」 ということがわかります。
このプロパティを使うと、joinする代わりに以下のように書けます。 

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var query = db.Products
                   .Where(x => x.Category.CategoryName == "Condiments")
                   .Select(x => new {
                      x.ProductID,
                      x.ProductName,
                      x.Category.CategoryName
                   });
     foreach (var p in query) {
         Console.WriteLine($"{p.ProductID} | {p.ProductName} | {p.CategoryName}");
     }
 }

※ SetLogging メソッドは、第3回目の記事で書いたメソッド。
※ Console.WriteLIneのところで、C#6.0の機能を使ってます。

これは、CategoryNameが"Condiments"であるProductを取得しているのですが、 CategoryNameは、ProductsテーブルではなくCategoryテーブルにあるカラムであることに 注目です。
関連するテーブルをC#で慣れ親しんだプロパティを通じてアクセスできるわけです。
実際発行されるSQLは、

SELECT 
    [Extent1].[ProductID] AS [ProductID], 
    [Extent1].[ProductName] AS [ProductName], 
    [Extent2].[CategoryName] AS [CategoryName]
    FROM  [dbo].[Products] AS [Extent1]
    INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
    WHERE N'Condiments' = [Extent2].[CategoryName]

と join していることが確認できます。
もし、Customerプロパティを使わないとすると、joinを使って、

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var query = from p in db.Products
                 join c in db.Categories on 
                           p.CategoryID equals c.CategoryID
                 where c.CategoryName == "Condiments"
                 select new {
                     p.ProductID,
                     p.ProductName,
                     c.CategoryName
                 };
     foreach (var p in query) {
         Console.WriteLine($"{p.ProductID} | {p.ProductName} | {p.CategoryName}");
     }
 }

と、より複雑なクエリ式を書く必要があります。
発行される SQLは、以下のように先ほどと同じものになっています。

SELECT 
    [Extent1].[ProductID] AS [ProductID], 
    [Extent1].[ProductName] AS [ProductName], 
    [Extent2].[CategoryName] AS [CategoryName]
    FROM  [dbo].[Products] AS [Extent1]
    INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
    WHERE N'Condiments' = [Extent2].[CategoryName]


  

Posted by gushwell at 22:00Comments(1)TrackBack(0)

2015年09月24日

EntityFramework(5):DbContextクラス

   このエントリーをはてなブックマークに追加 Clip to Evernote
DbContextは、EntityFrameworkの最も中心となるクラスです。

DBにアクセスするには、次のようにDbContextから派生したクラスのインスタンスを 生成し、データベースにアクセスする準備をします。

 var db = new NorthwindContext();

このシリーズの第1回で見たように、Visual Studio の[データベースからCode First] でソースを 作成すると、必ず、DbContextから派生したカスタムクラスのコードが作成されます。
作成されたNorthwindContext.cs の中身を見てみると、

  public partial class NorthwindContext : DbContext {
      public NorthwindContext()
          : base("name=NorthwindConnection") {
      }
      ...

と、NorthwindContextは、DbContext の派生クラスしていることが確認できます。
コンストラクタで

  base("name=NorthwindConnection")

としていますが、これは、データベースへの接続に、configファイルの connectionStrings で指定してある NorthwindConnection 接続文字列を使うことを示しています。

なお、partialクラスに、

   public NorthwindContext(string nameOrConnectionString) 
       : base(nameOrConnectionString) {

   }

といったコンストラクタを定義すれば、以下のように直接接続文字列を指定して接続することもできます。

 string connectionString = "data source=(LocalDB)...";
 var db = new NorthwindContext(connectionString);


DbContextには、Database, Configuration, ChangeTracker というプロパティがあります。 Databaseプロパティは、すでにLog出力のところでちょっとだけ触れましたね。 これら3つのプロパティについては、必要に応じて取り上げるつもりです。

それと、DbContextはスレッドセーフではないので、マルチスレッドアプリケーションで、
複数のスレッドで同一のDbContextインスタンスを使いまわすようなことはできません。

 

   
Posted by gushwell at 20:28Comments(1)TrackBack(0)

2015年09月17日

EntityFramework(4):遅延実行と即時実行

   このエントリーをはてなブックマークに追加 Clip to Evernote
▪️遅延実行

LINQを使ったクエリ操作は、次の3つの手順を取ります。

 1.データ ソースを取得します。 
 2.クエリを作成します。 
 3.クエリを実行します。

以前にもお見せした以下のコード(今回はメソッド形式にしています)と 照らし合わせてみましょう。

 using (var db = new NorthwindContext()) {
     var query = db.Products
                   .Where(x => x.UnitPrice < 10);
     foreach (var p in query)
         Console.WriteLine("{0} | {1} | {2} | {3}",
             p.ProductID, p.ProductName, p.CategoryID, p.UnitPrice);
 }

NorthwindContext() をnewしている箇所が、「データソースを取得」してい るところです。 EntityFrameworkでは、必ず、DbContextを生成するところから操作が始まります。
次にクエリを作成し変数queryに代入しています。重要なのは、ここではまだクエリは発行されていないということです。 単に「クエリ式」がquery変数に代入されているだけです。
実際のクエリは、foreachステートメントでクエリ変数が反復処理されるまで延期されます。これを「遅延実行」と言います。LINQ to Objectと同じですね。

この性質を利用すれば、最新のデータを取得するクエリを一つ用意し、それを、何回も使うということが出来ます。この場合、(その間でデータが更新されていれば)毎回異なった結果を得ることになります。

さらに遅延実行の性質から、以下のようにクエリ式をつなげることができます。

 using (var db = new NorthwindContext()) {
     var query = from product in db.Products
                 where product.UnitPrice < 10
                 select product;
     var query2 = from product in query  // queryを入力にしている
                  where product.ProductName.StartsWith("T")
                  select product;
     foreach (var p in query2)
         Console.WriteLine("{0} | {1} | {2} | {3}",
             p.ProductID, p.ProductName, p.CategoryID, p.UnitPrice);
 }

これは、遅延実行の利点の 1 つと捕らえることができます。
単純なクエリ式の組み合わせで、複雑なクエリを作り出すことが出来るのです。
もちろん、実施のSQLが発行されるときには、一つのSQL文として発行されます。

※ここで示したサンプルは、あくまでも、機能を示すためのものです。 この程度ならば、わざわざ2つに分割する必要はありませんね。


▪️即時実行

一方、Count,Max, First, Sum などSQLの集計関数を実行するクエリでは 遅延実行はされません。
例えば、

 var count = db.Products
               .Where(x => x.UnitPrice < 10)
               .Count();

というステートメントでは、即SQL文が発行され、count変数にその結果が格納され ます。
遅延実行されるクエリについて、即時実行を強制し、その結果をキャッシュした い場合には、ToList()、ToArray()を呼び出します。

  var list = db.Products
               .Where(x => x.UnitPrice < 10)
               .ToList();

とすれば、ToList()メソッドを呼び出した時点で、SQLが発行され、その結果が、 Listに変換されます。
これ以降 list変数へのアクセスでは、SQL文が発行されることはありません。
 
  
Posted by gushwell at 20:13Comments(0)TrackBack(0)

2015年09月14日

EntityFramework(3):Logプロパティ

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回は、Logプロパティについて。次のコードで、どういったSQL文が発行されているか確認したいとします。

 using (var db = new NorthwindContext()) {
     var maxprice = db.Products.Select(p => p.UnitPrice).Max();
     Console.WriteLine("{0}", maxprice);
 }
それは、以下のような行を、NorthwindContext()を生成した直後に書いておくだけです。これで発行されたSQL文がコンソールに出力されます。

  db.Database.Log = sql => { Console.Write(sql); };


ただ、このシリーズでは、クエリの結果をコンソールに出力しているので、 SQL文もコンソールに出力されると結果の確認が面倒なので、SQL文はファイルに出力するようにしましょう。 とりあえず、以下のような 簡易Log クラスを定義します。

    static public class Log {
        public static void Write(string text) {
            File.AppendAllText("log.txt",text + Environment.NewLine);

        }
    }

実際は、NLog とか Log4Net とか使ってください。

で、最初に示したコードを以下のように書き換えます。

    using (var db = new NorthwindContext()) {
        db.Database.Log = sql => { Log.Write(sql); };
        var maxprice = db.Products.Select(p => p.ListPrice).Max();
        Console.WriteLine("{0}", maxprice);
    }

実際に実行してみてください。 
log.txt を見てみると、以下のSQL文が発行されているのを確認できます。

SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        MAX([Extent1].[UnitPrice]) AS [A1]
        FROM [dbo].[Products] AS [Extent1]
    )  AS [GroupBy1]

なんか想像してたものと違ってましたが、 まあ、ここは EntityFramework の内部の仕様の話なので深くは考えないことにします。

でも、いちいち

 db.Database.Log = sql => { Log.Write(sql); };

と書くのも面倒です。NorthwindContext クラスは、partial クラスとして定義されているので、
以下のようなクラスを別途定義しておきましょう。

    public partial class NorthwindContext : DbContext {
        public void SetLogging() {
            this.Database.Log = sql => { Log.Write(sql); };
        }
    }
そうすれば、NorthwindContext のインスタンスを生成した後に、
 db.SetLogging();
と書けばログ出力が可能になります。
なお、プロジェクトの作成時は、DatabaseFirstだけど、それ以降は、CodeFirstで 開発するというのであれば、自動生成された NorthwindContext.cs のコンストラクタ なりに、ログ出力の設定をしてしまっても良いかもしれませんね。
 

最後に、もうひとつ、どんなSQLが発行されるのか確認してみましょう。

   var query = db.Products
                 .OrderByDescending(x => x.UnitsInStock)
                 .Take(10);

このクエリの場合は、以下のSQLが発行されるのが確認できました。

SELECT TOP (10) 
    [Extent1].[ProductID] AS [ProductID], 
    [Extent1].[ProductName] AS [ProductName], 
    [Extent1].[SupplierID] AS [SupplierID], 
    [Extent1].[CategoryID] AS [CategoryID], 
    [Extent1].[QuantityPerUnit] AS [QuantityPerUnit], 
    [Extent1].[UnitPrice] AS [UnitPrice], 
    [Extent1].[UnitsInStock] AS [UnitsInStock], 
    [Extent1].[UnitsOnOrder] AS [UnitsOnOrder], 
    [Extent1].[ReorderLevel] AS [ReorderLevel], 
    [Extent1].[Discontinued] AS [Discontinued]
    FROM [dbo].[Products] AS [Extent1]
    ORDER BY [Extent1].[UnitsInStock] DESC
  
Posted by gushwell at 21:30Comments(0)TrackBack(0)

2015年09月10日

EntityFramework(2):テーブルからデータを取得する

   このエントリーをはてなブックマークに追加 Clip to Evernote
前回は、「EntityFramework事始め」と題して、Database FirstでのEntityFrameworkの簡単な使い方を説明しましたが、今回は、NorthwindデータベースのProductsテーブルを使って、いくつかの簡単 なクエリを書いてみました。

最初のサンプルは、これ。
 

 using (var db = new NorthwindContext()) {
     var query = from product in db.Products
                 where product.UnitPrice < 10
                 select product;
     foreach (var p in query)
         Console.WriteLine("{0} | {1} | {2} | {3}",
             p.ProductID, p.ProductName, p.CategoryID, p.UnitPrice);
 }


Productsテーブルから、UnitPriceが10より小さいものを取得しています。 結果は省略します。


では、次の例。
 

 using (var db = new NorthwindContext()) {
     var maxprice = db.Products.Select(p => p.UnitPrice).Max();
     Console.WriteLine("{0}", maxprice);
 }


Productsテーブルから最も高い単価(UnitPrice)が幾らなのかを見つけています。
クエリ構文ではなくメソッド構文を使った例です。
クエリ構文苦手なので、以降、断りなしにメソッド構文使っていきます。 たまに、気が向いたらクエリ構文使います。
 

次は、UnitsInStockで降順に並び替え、先頭の10製品を求める例です。
 

 using (var db = new NorthwindContext()) {
     var query = db.Products
                   .OrderByDescending(x => x.UnitsInStock)
                   .Take(10);
     foreach (var p in query)
         Console.WriteLine("{0} | {1} | {2} | {3}",
             p.ProductID,  p.UnitsInStock, p.ProductName, p.CategoryID);
 }



最後の例です。
  

 using (var db = new NorthwindContext()) {
     var query = db.Products
                   .Where(x => x.CategoryID == 5)
                   .Select(x => new {
                       x.ProductID,
                       x.CategoryID,
                       x.ProductName,
                       x.UnitPrice
                   });
     foreach (var p in query)
         Console.WriteLine("{0} | {1} | {2} | {3}",
             p.ProductID, p.ProductName, p.CategoryID, p.UnitPrice);
 }


CategoryIDが5の製品を取得しています。その際、データベースからは、 ProductID, ProductName, CategoryID,UnitPrice だけを取得しています。 匿名クラスを使っています。
LINQ to Objectが分かっていれば、どれも、どうってことのないクエリですね。

  
Posted by gushwell at 21:30Comments(0)TrackBack(0)

2015年09月06日

EntityFramework(1):Database FirstでEntityFramework事始め

   このエントリーをはてなブックマークに追加 Clip to Evernote

前回の投稿から1か月以上たってしまいました。これまではLINQ to Objectのサンプルコードを掲載してきましたが、今度は、Entity Frameworkついて書いていこうと思います。
と言っても、Entity Frameworkの範囲が広すぎて、僕の手にはおえないので、その一部の機能に限定されると思いますが...

いつものように、一つ一つの記事は短めに。

僕の環境は、
1 Visual Studio Professional 2015
2 EntityFramework 6.1.3
です。

ところで、Visual Studio Community 2015 がダウンロードできるようになりましたね。
学生、オープン ソース貢献者、小規模チームは無料で利用できます。 


さて、話を戻すと、サンプルデータベースとしては、相も変わらず Northwind を使います。
つまり、Code Firstではなく、Database First でサンプルコードを作成していきます。
といっても、最初のPOCOクラスを自動生成してしまえば、それ以降は、Code First とそれほど変わるところはありません。


SQL Server 2014で、Northwindデータベースをお使いになる場合は、 石坂忠広さんの記事を参考にしてください。
SQL Server 2014にNorthwindデータベースをインストールする

■プロジェクトの作成とサンプルDBの追加

C#のプロジェクトの新規作成します。 EntityFrameworkの機能にフォーカスするために、 ここではコンソールアプリケーションを選択します。
便宜上、プロジェクト名は、EFSample という名前で話を進めます。

プロジェクトができたら、次に、App_Data フォルダをプロジェクトに新規追加します。
作成されたVisual StudioのプロジェクトのApp_Dataフォルダに、エクスプローラから Northwnd.mdf と Northwnd_log.ldf をドロップします。
ファイルが App_Dataフォルダにコピーされると同時に、プロジェクトに追加されます。

c01

■Entity Data Model 作成

  1. プロジェクトEFSample に Models フォルダを作成します。

  2. Model フォルダを右クリックし、[新しい項目の追加]を選びます。

  3. 新しい項目の追加ダイアログが表示されますので、[ADO.NET Entity Data Model ]を選択し、名前に "NorthwindContext"と入力し、[追加]ボタンを押します。

    c02

  4. [データベースからCode First] を選択し、次へ ボタンをクリックします。

    c03

    ※ 変な名前ですね。もっと良い訳はなかったんでしょうか?

  5. 新しい接続ボタンをおして、設定のプロパティダイアログで、データソースの 変更で、Microsoft SQL Server データベースファイル を選びます。

    C051

    c04


  6. データベースのファイル名では、先ほどのNorthwnd.mdf を指定し、OK ボタンを押します。

  7. ダイアログの下の入力欄に、"NorthwindConnection" と入力し、次へを押します。

    C52

  8. 「ファイルをプロジェクトにコピーし、接続を変更しますか?」 と聞いてきますが、いいえを選択します。
    ※ はっきり言ってこのメッセージは不要ですね。はいを押すとプロジェクトの直下に mdfファイルがコピーされてしまいます。

  9. 次のウィザード画面では、すべてのテーブルをモデルに含めるので、 テーブルにチェックを入れます。 さらに、「生成されたオブジェクトの名前を複数化または単数化する」にチェックし、 完了ボタンを押します。

    c08

  10. Models フォルダに、NorthwindContext.cs と、各テーブルに対応した C#のソースフィルが追加されていることを確認します。

    c09


実際に、これらのソースファイルに中身を確認してください。 いくつかの属性が追加されてはいますが、普通のC#のクラスである ことを確認できると思います。

NorthwindContext.cs には、Categories や Customersといった テーブル名を複数形にしたプロパティが定義されています。 EntityFramework では、これらのプロパティを使い、テーブルにアクセス することになります。


次のバージョンのEntityFrameworkでは、EDMXファイル([データベースからEF Degigner]で作成される)がサポートされないことがアナウンスされています。
そのため、このシリーズでは、EDMXを使わずに、[データベースからCode First]で、エンティティモデル を作成しています。Database Firstでも問題なくプログラムを書くことができます。

■Productsテーブルの参照

Productsテーブルからデータを取得してみましょう。
プログラムでは、先ほど作成した Product「EntityFrameworkエンティティクラス」を 経由して、Productsテーブルの各カラムにアクセスすることになります。 さっそく、このProductsテーブルにアクセスするコードを書いてみます。

 using LinqToEntitySample.Models;
 ...

 static void Main(string[] args) {
     using (var db = new NorthwindContext()) {
         var query = from product in db.Products
                     select product;
         foreach (Product p in query)
             Console.WriteLine("{0} | {1} | {2} | {3}",
                 p.ProductID, p.ProductName, p.UnitPrice, p.QuantityPerUnit);
         Console.ReadLine();
     }
 }

実行すると、


  1 | Chai | 18.0000 | 10 boxes x 20 bags
  2 | Chang | 19.0000 | 24 - 12 oz bottles
  3 | Aniseed Syrup | 10.0000 | 12 - 550 ml bottles
  4 | Chef Anton's Cajun Seasoning | 22.0000 | 48 - 6 oz jars
  5 | Chef Anton's Gumbo Mix | 21.3500 | 36 boxes
  6 | Grandma's Boysenberry Spread | 25.0000 | 12 - 8 oz jars
  7 | Uncle Bob's Organic Dried Pears | 30.0000 | 12 - 1 lb pkgs.
 .
 .
 .

と表示され、Productsテーブルからデータを取得できたことを確認できます。
NorthwindContext クラスは、[データベースからCode First]の追加で自動生成されたクラ スで、NorthwindContext.csで以下のように定義されています。

    public partial class NorthwindContext : DbContext {
        public NorthwindContext()
            : base("name=NorthwindContext") {
        }

        public virtual DbSet Categories { get; set; }
        public virtual DbSet CustomerDemographics { get; set; }
        public virtual DbSet Customers { get; set; }
        public virtual DbSet Employees { get; set; }
        public virtual DbSet Order_Details { get; set; }
        public virtual DbSet Orders { get; set; }
        public virtual DbSet Products { get; set; }
        public virtual DbSet Regions { get; set; }

       以下省略

先ほど利用した Productsプロパティも定義されていますね。 EntityFrameworkでは、このDbContextクラスの派生クラスのインスタンスを生成 することで、データベースにアクセスすることが可能になります。

先ほどのクエリ式では、Productsテーブルから全てのデータを取得しています。各カラムの値は、プロパティとしてアクセスできます。
なお、Productsテーブルを示すNorthwindContextクラスのプロパティ名はProductsと複数形になっている点に注目してください。単数形のProductは、1行分を表すクラスです。

今回は、これで終わり。
次回からは LINQ to Entitiesを使ったクエリについて書いていきます。

  
Posted by gushwell at 21:30Comments(0)TrackBack(0)