2015年11月25日

EntityFramework(20):楽観的同時実行制御 (Timestamp列なし)

   このエントリーをはてなブックマークに追加 Clip to Evernote
前回は、Timestamp型/rowversion型のカラムがテーブルに用意されている場合の楽観的同時実行制御について書きましたが、今回は、Timestamp型/rowversion型のカラムが無い場合の楽観的同時実行制御について。

Database Firstで開発している時って、種々の理由でRowVersion列をテーブルに追加 できないことも結構あります。
でも大丈夫、ちょっと面倒くさいけど、そんなときにも対応可能です。
まず、以下のようなpartialクラスを定義します。 classが入れ子になっている点に注意。Metadataクラスに中に、Categoryテーブルの主キー以外の プロパティを定義し、ConcurrencyCheck属性を付加。

 [MetadataType(typeof(Category.Metadata))]
 public partial class Category {
     class Metadata {
         [ConcurrencyCheck]
         public string CategoryName { get; set; }

         [ConcurrencyCheck]
         public string Description { get; set; }

         [ConcurrencyCheck]
         public byte[] Picture { get; set; }
     }
 }

更新のコードは、前回示したものと同じです。

 using (var db = new NorthwindContext()) {
     try {
         db.SetLogging();
         Category cat = db.Categories.First(x => x.CategoryName == "Produce");
         cat.CategoryName = "UpdatedCategory";
         db.SaveChanges();
     } catch (DbUpdateConcurrencyException ex) {
         Console.WriteLine(ex.ToString());
     }
 }

上の更新のコードを実行すると、次のようなSQL文が発行されます。
読み込んだ時の値と、Updateしようとしている時のDBの値が同じかどうかを判断してくれるSQL文になってます。

UPDATE [dbo].[Categories]
SET [CategoryName] = @0
WHERE (((([CategoryID] = @1) AND ([CategoryName] = @2)) 
  AND [Description] IS NULL) AND [Picture] IS NULL)
  

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

2015年11月22日

EntityFramework(19):楽観的同時実行制御 (Timestamp列あり)

   このエントリーをはてなブックマークに追加 Clip to Evernote
EntityFrameworkでは、楽観的同時実行制御の仕組みが組み込まれています。
もっとも簡単な方法は、Timestamp型/rowversion型のカラムをテーブルに用意することです。
Database Firstの場合、Timestamp型/rowversion型のカラムがある場合は、 Visual Studioが自動生成するエンティティクラスに以下のようなプロパティが 生成されます。

        [Column(TypeName = "timestamp")]
        [MaxLength(8)]
        [Timestamp]
        public byte[] RowVersion { get; set; }

※ これは、カラム名を RowVersionとして定義した場合の例です。

あとは、EntityFrameworkが自動でたとえば、楽観的同時実行制御のためのSQLを発行してくれます。

例えば、以下のコードを例に取ると、

 using (var db = new NorthwindContext()) {
     try {
         db.SetLogging();
         Category cat = db.Categories.First(x => x.CategoryName == "Produce");
         cat.CategoryName = "UpdatedCategory";
         db.SaveChanges();
     } catch (DbUpdateConcurrencyException ex) {
         Console.WriteLine(ex.ToString());
     }
 }

Categoriesテーブルに RowVersion列がある場合、 以下のような UPDATE文を発行します。

UPDATE [dbo].[Categories]
SET [CategoryName] = @0
WHERE (([CategoryID] = @1) AND ([RowVersion] = @2))

パラメータ p1, p2... には、変更前の値が渡されます。
これによって、RowVersion列が取得した値ではない値に更新されていた場合は、DbUpdateConcurrencyException 例外が発生します。
   
Posted by gushwell at 22:00Comments(0)TrackBack(0)

2015年11月18日

EntityFramework(18):トランザクション処理

   このエントリーをはてなブックマークに追加 Clip to Evernote
EntityFrameworkでトランザクションを利用するには、以下の3種類の方法があります。

  1. 暗黙のトランザクションを利用する 
  2. BeginTransactionメソッドを利用する 
  3. TransactionScopeクラスを利用する 


■ 暗黙のトランザクションを利用する

SaveChangesが呼び出されると、EntityFrameworkは、呼び出しがトランザクション のスコープにあるかどうかを判断し、トランザクションのスコープに無い場合に、 自動的にローカルトランザクションを開始し、生成したSQL コマンドを実行しま す。
すべての SQL コマンドが終了すると、EntityFramework はローカル トランザク ションをコミットして戻ります。
これで十分な場面はけっこうあると思います。


■ BeginTransactionメソッドを利用する

データベースからデータを取得し、何らかの加工をしてから、データを更新する ようなシチュエーションで利用します。
ここでは、面倒なので、単にINSERTするだけのコードにします。

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     using (var tx = db.Database.BeginTransaction(System.Data.IsolationLevel.Serializable)) {
         try {
             Category cat = new Category { CategoryName = "New Category" };
             Product product = new Product { ProductName = "New Product", Category = cat };
             db.Products.Add(product);
             db.SaveChanges();
             tx.Commit();
         } catch (DbUpdateException ex) {
             tx.Rollback();
         }
     }
 }

Commit とか、Rollbackとは書かないといけないのが面倒ですね。
でも、EntityFrameworkで明示的にトランザクションをかけたい時は、この方法が標準でしょうか?


■ TransactionScopeクラスを利用する

本来の使い方は、「複数のデータベースに対する読み書き処理」を ひとつのトランザクションに束ねるために利用するものです。
でも、一つのデータベースしか使わない場合は、MS-DTCの昇格は行われません。 なので、暗黙のトランザクションを利用するメソッドが複数あって、 これを束ねて一つのトランザクションとして処理したい場合にも利用できます。 これ使えば実装の柔軟性が随分とあがるというのが個人的な感想です。
以下、コードを載せます。

 using (var tx = new TransactionScope()) {
     using (var db = new NorthwindContext()) {
         db.SetLogging();
         Category cat = new Category { CategoryName = "New Category" };
         db.Categories.Add(cat);
         db.SaveChanges();
     }
     using (var db = new NorthwindContext()) {
         db.SetLogging();
         Category cat = db.Categories.First(x => x.CategoryName == "New Category");
         Product product = new Product { ProductName = "New Product", Category = cat };
         db.Products.Add(product);
         db.SaveChanges();
     }
     tx.Complete();
 }

Complete() 忘れると、コミットされないので注意してください。なお、Rollbackは勝手にやってくれます。

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

2015年11月15日

EntityFramework(17):SQL ステートメントの直接実行

   このエントリーをはてなブックマークに追加 Clip to Evernote
EntityFrameworkでは、SQL文を直接実行することもできます。
使う場面は限られると思いますが、この機能があるとなにかと便利です。

  using (var db = new NorthwindContext()) {
      db.SetLogging();
      var q = @"select * from Customers where Country = @p0 and City = @p1";
      var customers = db.Database.SqlQuery(q, "Spain", "Madrid");
      foreach (var cust in customers) {
          Console.WriteLine($"{cust.CustomerID}, {cust.CompanyName}, {cust.ContactTitle}");
      }
  }

SqlParameter使うと、こんな風にも書けます。

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var q = @"select * from Customers where Country = @country and City = @city";
     var param1 = new SqlParameter("@country", "Spain");
     var param2 = new SqlParameter("@city", "Madrid");
     var customers = db.Database.SqlQuery(q, param1, param2);
     foreach (var cust in customers) {
         Console.WriteLine($"{cust.CustomerID}, {cust.CompanyName}, {cust.ContactTitle}");
     }
 }

INSERT, UPDATE, DELETE を発行したい場合は、SqlQuery の代わりに、ExecuteSqlCommand を使います。
戻り値は、削除した件数などコマンド実行後にデータベースによって返される結果となります。 

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     var q = @"exec CustOrderHist @p0";
     var customers = db.Database.SqlQuery(q, "ALFKI");
     foreach (var cust in customers) {
         Console.WriteLine($"{cust.ProductName}, {cust.Total}");
     }
 }

 ...
 public class CustOrderHist {
     public string ProductName { get; set; }
     public int Total { get; set; }
 }

ストアドプロシージャもSqlQuery、ExecuteSqlCommand を使って呼び出すことができます。
   
Posted by gushwell at 22:30Comments(0)TrackBack(0)

2015年11月11日

EntityFramework(16):IDENTITY列の扱い

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回はIDENTITY列についてです。

プロパティに "ID" (大文字と小文字の区別なし) という名前が付いていて、型が数値の場合、EntityFrameworkは、そのプロパティが主キーであると認識します。 かつ、IDENTITY列として扱ってくれます。
「データベースからCode First」を利用する場合、このあたりはほとんど意識する 必要はありません。
自分でPOCOクラスを定義する場合で、IDという名前が付かない場合は、

[DatabaseGenerated(DatabaseGenerationOption.Identity)] 
public int CustomerCode { get; set; }

などと、DatabaseGenerated 注釈をつけてあげる必要があるみたいです。

このIDENTITY列の値は、行を挿入した時に割り振られるわけですが、 どうやってC#のコード側でこの値を把握するのか、これも実に簡単です。

 using (var db = new NorthwindContext()) {
     Category cat = new Category { CategoryName = "New Category" };
     //Product product = new Product { ProductName = "New Product", Category = cat };
     db.Categories.Add(cat);
     db.SaveChanges();
     Console.WriteLine($"CategoryID={cat.CategoryID}");
 }

あるいは、以下のように書くこともできます。

 using (var db = new NorthwindContext()) {
     Category cat = new Category { CategoryName = "New Category" };
     //Product product = new Product { ProductName = "New Product", Category = cat };
     var newcat = db.Categories.Add(cat);
     db.SaveChanges();
     Console.WriteLine($"CategoryID={newcat.CategoryID}");
 }

Add()メソッドの戻り値を利用すれば、メソッドチェインさせることもできます。

どんなSQL文が発行されるのかを確認してみました。

INSERT [dbo].[Categories]([CategoryName], [Description], [Picture])
VALUES (@0, NULL, NULL)
SELECT [CategoryID]
FROM [dbo].[Categories]
WHERE @@ROWCOUNT > 0 AND [CategoryID] = scope_identity()

-- @0: 'New Category' (Type = String, Size = 15)

わずらわしいSQL文のコーディングから解放されるのは、とても嬉しいですね。
  
Posted by gushwell at 22:00Comments(0)TrackBack(0)

2015年11月08日

EntityFramework(15):正規関数を利用する

   このエントリーをはてなブックマークに追加 Clip to Evernote
クエリの中で SQLの DATEDIFF 関数を呼び出したい時、どうすればよいでしょうか?
Entity Frameworkでは、DbFunctions クラスの静的メソッドとして、たくさんの関数(これを正規関数と呼んでいる)を定義しています。
分散や標準偏差を求める関数もこの中にはあります。詳しくは、このページを見てください。

DATEDIFF関数に対応する DiffDays メソッドも用意されています。

このDiffDaysを使ったコードをいかに示します。

            using (var db = new NorthwindContext()) {
                var orders = db.Orders
                               .Where(x => DbFunctions.DiffDays(x.OrderDate, x.ShippedDate) < 2);
                foreach (var order in orders) {
                    Console.WriteLine($"{order.OrderID}, {order.OrderDate}, {order.ShippedDate}");
                }
            }

発行されるSQLは、以下の通りです。

SELECT
   [Extent1].[OrderID] AS [OrderID],
   [Extent1].[CustomerID] AS [CustomerID],
   [Extent1].[EmployeeID] AS [EmployeeID],
   [Extent1].[OrderDate] AS [OrderDate],
   [Extent1].[RequiredDate] AS [RequiredDate],
   [Extent1].[ShippedDate] AS [ShippedDate],
   [Extent1].[ShipVia] AS [ShipVia],
   [Extent1].[Freight] AS [Freight],
   [Extent1].[ShipName] AS [ShipName],
   [Extent1].[ShipAddress] AS [ShipAddress],
   [Extent1].[ShipCity] AS [ShipCity],
   [Extent1].[ShipRegion] AS [ShipRegion],
   [Extent1].[ShipPostalCode] AS [ShipPostalCode],
   [Extent1].[ShipCountry] AS [ShipCountry]
   FROM [dbo].[Orders] AS [Extent1]
   WHERE (DATEDIFF (day, [Extent1].[OrderDate], [Extent1].[ShippedDate])) < 2

DATEDIFF関数が使われていることが確認できます。

では、もう一つ。RIGHT関数を呼び出す例です。

 using (var db = new NorthwindContext()) {
     db.Database.Log = sql => { Console.Write(sql); };
     var customers = db.Customers
                    .Where(x => DbFunctions.Right(x.ContactName,6).ToLower() == "wilson");
     foreach (var cust in customers) {
         Console.WriteLine($"{cust.CustomerID}, {cust.CompanyName}, {cust.ContactName}");
     }
 }

発行されるSQL文は以下の通りです。

SELECT
    [Extent1].[CustomerID] AS [CustomerID],
    [Extent1].[CompanyName] AS [CompanyName],
    [Extent1].[ContactName] AS [ContactName],
    [Extent1].[ContactTitle] AS [ContactTitle],
    [Extent1].[Address] AS [Address],
    [Extent1].[City] AS [City],
    [Extent1].[Region] AS [Region],
    [Extent1].[PostalCode] AS [PostalCode],
    [Extent1].[Country] AS [Country],
    [Extent1].[Phone] AS [Phone],
    [Extent1].[Fax] AS [Fax]
    FROM [dbo].[Customers] AS [Extent1]
    WHERE N'wilson' = (LOWER(RIGHT([Extent1].[ContactName], cast(6 as bigint))))

このDbFunctionsクラスは、Entity Framework の以前のバージョンでは、EntityFunctions と呼ばれていたこともあります。
  
Posted by gushwell at 22:30Comments(0)TrackBack(0)

2015年11月04日

EntityFramework(14):リレーションのあるエンティティの削除

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回は、エンティティの削除について。

注文を削除する場合に、前回の最後のサンプルで示したAddメソッドを使った挿入と同じ感覚で、Removeメソッドを使うとどうなるでしょうか?

 // まずは、確認用に挿入
 using (var db = new NorthwindContext()) {
     Category cat = new Category { CategoryName = "New Category" };
     Product product = new Product { ProductName = "New Product", Category = cat };
     db.Products.Add(product);
     db.SaveChanges();                
 }

 // Removeメソッド呼び出し。
 using (var db = new NorthwindContext()) {
     var p = db.Products.First(x => x.ProductName == "New Product");
     var c = db.Categories.First(x => x.CategoryName == "New Category");
     c.Products.Remove(p);
     db.SaveChanges();
 }

 // Removeメソッドの結果確認
 using (var db = new NorthwindContext()) {
     var p = db.Products.First(x => x.ProductName == "New Product");
     var c = db.Categories.First(x => x.CategoryName == "New Category");
     var s1 = $"{p.ProductID} | {p.ProductName} | {p.CategoryID}";
     Console.WriteLine(s1);
     var s2 = $"{c.CategoryID} | {c.CategoryName}";
     Console.WriteLine(s2);
 }

結果は、

78 | New Product |
9 | New Category

となります。
1行目の最後のCategoryIDの値が表示されていません。
デバッグで中身を確認してみると、null になっています。
つまり、上のコードでは、関連が外れているだけで、テーブルからレコードが削除されているわけではないということです。

ProductのCategoryIDカラムが、null を許容しているからこういった状況が生まれます。

実際に、テーブルから該当するオブジェくトを削除するには、以下のようなコードを書く必要があります。

 using (var db = new NorthwindContext()) {
     var p = db.Products.First(x => x.ProductName == "New Product");
     var c = db.Categories.First(x => x.CategoryName == "New Category");
     c.Products.Remove(p);   // ★
     db.Categories.Remove(c);
     db.SaveChanges();
 }

★印の行はこのケースでは無くてもかまいません。
ProductのCategoryIDカラムが、null を許容していない場合は、★の行が必要です。
Productそのものもテーブルから削除し、そのあとに、Categoryテーブルから、該当する行を削除しています。

-----
あまりないシチュエーションだと思いますが、リレーションの変更もオブジェクト指向的なコードで実現できます。 ここでは、顧客間で注文を移動するコードを書いてみました。

 int orderID;
 using (var db = new NorthwindContext()) {
     Customer cust1 = db.Customers.Single(c => c.CustomerID == "ALFKI");
     Customer cust2 = db.Customers.Single(c => c.CustomerID == "FOLKO");
     // 最後の注文を取り出します。
     Order o = cust1.Orders.Last();
     orderID = o.OrderID;
     Console.WriteLine($"{o.OrderID}, {o.CustomerID}");
     // 顧客1から注文を削除し、その注文を別の顧客2に追加します。
     cust1.Orders.Remove(o);
     cust2.Orders.Add(o);
     db.SaveChanges();
 }
 // 移動できているかを確認
 using (var db = new NorthwindContext()) {
     var o= db.Orders.First(x => x.OrderID == orderID);
     Console.WriteLine($"{o.OrderID}, {o.CustomerID}");
 }

以下、結果です。確かに移動できました。

11011, ALFKI
11011, FOLKO

  
Posted by gushwell at 22:30Comments(3)TrackBack(0)

2015年11月01日

EntityFramework(13):リレーションのあるエンティティの更新、挿入

   このエントリーをはてなブックマークに追加 Clip to Evernote
前回はあまりにも単純な例でしたが、実際はテーブル間にはリレーションが張られています。リレーションが張られているテーブルについてみていきます。

Productsテーブルに新しい行を挿入する場合を考えてみます。Productのは、どのカテゴリに属するかを示すCategoryIdがあり、これがCategoriesテーブルの外部キーとなています。
このategoryIdに値を設定する必要があります。Entity Frameworkにおいても、これは同じです。 Productsテーブルに行を挿入するコードを示します。

 using (var db = new NorthwindContext()) {
     Product product = new Product {
         ProductName = "New Product",
         CategoryID = 1,
         UnitPrice = 10,
     };
     db.Products.Add(product);
     db.SaveChanges();
 }

実際に更新されたのかを確認するコードも書いてみます。上のコードの直後に 次のコードを挿入します。

 using (var db = new NorthwindContext()) {
     var q = db.Products.Where(x => x.ProductName == "New Product");
     foreach (var p in q) {
         var s = $"{p.ProductID}, {p.ProductName}, {p.CategoryID}, {p.Category.CategoryName}";
         Console.WriteLine(s);
     }
 }

以下のような結果を得ることができます。
 78, New Product, 1, Beverages


CategoryID に直接値を設定するのではなく、ProductのCategoryプロパティを設定することでも同じことができます。

 using (var db = new NorthwindContext()) {
     Category cat = db.Categories.First(x => x.CategoryName == "Seafood");
     Product product = new Product() {
         ProductName = "New Product",
         Category = cat
     };
     db.Products.Add(product);
     db.SaveChanges();
 }


また、Categoryテーブルに、Productを追加することで同様のことを実現できます。 上と似たようなコードですが、主従が逆転しているのがわかると思います。

 using (var db = new NorthwindContext()) {
     Category cat = db.Categories.First(x => x.CategoryName == "Seafood");
     Product product = new Product() {
         ProductName = "New Product",
     };
     cat.Products.Add(product);
     db.SaveChanges();
 }

最後に、Category とProductを同時に挿入するコードを示します。
 using (var db = new NorthwindContext()) {
     Category cat = new Category {
         CategoryName = "New Category"
     };
     Product product = new Product {
         ProductName = "New Product",
         Category = cat
     };
     db.Products.Add(product);
     db.SaveChanges();
 }


挿入した行を読み込むコードも書いてみます。

 using (var db = new NorthwindContext()) {
     var q = db.Products.Where(x => x.ProductName == "New Product");
     foreach (var p in q) {
         var s = $"{p.ProductID}, {p.ProductName}, {p.CategoryID}, {p.Category.CategoryName}";
         Console.WriteLine(s);
     }
 }

結果。
78, New Product, 9, New Category
このように、EntityFrameworkでは、オブジェクト指向的なプログラミングができるように設計されています。  
Posted by gushwell at 22:00Comments(0)TrackBack(0)