Windows 8 アプリとデータベース/サービスとの連携 – ① データモデル作成とデータベース生成

皆様、こんにちは!今期(7月以降)初めての投稿となります。今年もよろしくお願いします!

9/2(火) タッチ/ペン対応アプリの開発とクラウドを活用した既存サービスとの接続方法のセミナーにご参加いただきました皆様、お忙しいところありがとうございました。今後も、皆様の関心のあるテーマを中心にセミナーの企画とセッションのご提供をして行きたいと考えていますので、今後ともよろしくお願いします。

さて、早速、お約束した通り、私が担当しましたこのセッションの詳細情報とコードにつき、ご紹介していきたいと思います。

image

概要

Windows ストアアプリからデータベースへの連携を行う場合には、直接のデータ連携はできず、Web サービス経由で行う必要があります。ここでは、SQL Server Express Edition 上に生成されたデータベースを、ASP.NET Web APIから、REST インターフェースとJSON形式のペイロードを使用して公開し、それをWindows ストアアプリから利用するシナリオをご紹介します。

ADO.NET Entity Framework 5.0以降で実装された、Code First の機能を使い、Visual Studio 上のソースコードをビルドすることにより、SQL Server Express Edition 上に、各カラムの属性やリレーションシップ等のメタ情報を含めつつデータベースを生成することが可能です。また、Migration 機能により、カラムの追加等のデータベースへの変更を、データに影響を与えることなく行うことが可能です。ここで出来上がったデータベースを、ASP.NET Web API を使い、REST のインターフェースで公開します。

ここでは、商品管理用のWindows ストアアプリを実装します。これらの機能を実現するために、SQL Server Express Edition 、ADO.NET Entity Framework 6.0、Code First、ASP.NET Web API、そして Windows ストアアプリのテンプレートの各技術の基本的な使い方をご紹介します。

このエントリは、① バックエンドサービス作成 ① -1 データモデル作成とデータベース生成について、ご紹介します。

必要なソフトウェア他

· Microsoft Windows 8.1

· Microsoft Visual Studio 2013

· SQL Server 2012 Express Edition

サンプル:タイムセール商品管理の構成

業務用のWindows ストアアプリ(以下、ビジネスストアアプリ)では、企業内外のデータソースへのアクセスが重要となります。ただ、Windows ストアアプリの場合、データベースに直接接続はできず、Web サービスを経由して接続することになります。

今回作成するビジネス Windows ストアアプリ

スーパーマーケットのタイムセールを管理するストアアプリとなります。アーキテクチャー的には、このバックエンドサービスは、Windows サーバーでも、Windows Azure でもほぼ同じコードで配置可能となります。TimeSaleStoreAppと呼ばれる、商品情報と商品売価を管理するアプリケーションに、全ての機能を統合します。Windows ストアアプリとして、デザイン要件を満たす必要があるほか、データとデータ ソースへの接続が必要です。マイクロソフトの最新の O/RM フレームワークである、Entity Framework 6.0 を使用してデータモデルを実装します。データモデルが作成できたら、ASP.NET Web API を介し、REST サービスを通じて利用できるリモートデータを使用して、クライアント側(Windows ビジネスストアアプリ)の開発が可能になります。最後にWindows ビジネスストアアプリにデータを提示するための UI を開発します。Visual Studio 2013 に準備されているWindows ストアアプリ用のテンプレートを使い、C#/XAML で開発します。HttpClient を使います。また、共有コントラクトを実装し、このアプリに Windows 8.1 の共有機能を統合します。

データベースのモデル

今回のアプリのデータベースのモデルは、スーパーマーケットの食料品の商品の売価管理のサンプルで、非常に簡単です。1つのテーブルの中に、Product(商品)とProductPrice(商品の売価)を持っています。商品の売価は、価格設定日が一番最近のものが反映される、という仕組みで、過去の売価設定の履歴を全部持っているという仕様です。

店舗

売価変更時刻

商品

売価

変更の理由

決定した日付時刻

担当者

Code Firstアプローチ

まずはデータベースモデルを実装するために、Entity Framework 6.0 および Code First の技術を使用します。Visual Studio 2013 を起動し、新しいプロジェクト-> Windows -> クラスライブラリを選択します。

 

Code First とは、既存の Database First パターンや Model First パターンの代わりとなる、Entity Framework の新しい開発パターンです。Code First により、.NET の CLR クラスを使用してモデルを定義できるようになります。

image

image

その後、これらの CLR クラスを既存のデータベースにマップしたり、CLR クラスを使用してデータベース スキーマを生成したりすることができます。したがって、SQL データベースは、このセクションの最後に作成されます。

image

適当な名前(ここではTimeSale)で空のクラスライブラリプロジェクトを作成したら、エンティティの実装に入ります。先ほどのER 図に示した通り、1つの .NET クラスを作ります。ProductwithPriceです。そのために必要な手順は下記の通りです。

クラスライブラリプロジェクトの作成

最初に、Entity Framework 6.0 を追加します。クラスを実装する前に、Entity Framework への参照を追加する必要があります。現在、Entity Framework は .NET から分離されていますので、NuGet を使ってこれを行います。参照設定を右クリックして、NuGet パッケージの管理をクリックします。

image

検索ボックスで、Entity Framework とタイプして、6.0が見つかったらエンターキーを押します。当該パッケージがリストに追加されます。インストールボタンをクリックしてインストールしてください。下の図は、NuGet パッケージの管理ダイアログボックスです。License Agreement に同意して、ダイアログボックスを閉じます。

image

次に、クラスを追加します。プロジェクトを右クリック→新しいクラスを追加、で行います。

ProductwithPrice店舗ID Key )、店舗、売価変更時刻、商品、売価、変更の理由、決定した日付時刻、担当

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Linq;
    4: using System.Text;
    5: using System.Threading.Tasks;
    6: using System.ComponentModel.DataAnnotations;
    7:  
    8: namespace TimeSale
    9: {
   10:     public partial class ProductwithPrice
   11:     {
   12:         [Key]
   13:         public int StoreId { get; set; }
   14:         public string StoreName { get; set; }
   15:         public string PriceChangedDate { get; set; }
   16:         public string ProductName { get; set; }
   17:         public string RetailPrice { get; set; }
   18:         public string ReasonforChangePrice { get; set; }
   19:         public string PriceFixedDate { get; set; }
   20:         public string Contact { get; set; }
   21:     }
   22: }

Primary Key を設定するには、System.ComponentModel.DataAnnotationsusing 句で追加し、 [Key] のメタタグを付けます。

DB コンテキストの作成

これでエンティティができましたので、DBContext を作成します。これにより、データベースに対する全てのCRUD(読み取り・更新・追加・削除)の処理が管理できます。

TimeSaleApp.cs ファイル追加と DataContext クラスの実装

DBContextクラスを下記の通り実装します。最初に、System.Data.Entityusing 句に追加しておきます。

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Data.Entity;
    4: using System.Linq;
    5: using System.Text;
    6: using System.Threading.Tasks;
    7:  
    8: namespace TimeSale
    9: {
   10:     public class TimeSaleAppContext:DbContext
   11:     {
   12:         public TimeSaleAppContext() : 
   13:             base("productwithprice_db")
   14:         {
   15:             Configuration.LazyLoadingEnabled = false;
   16:         }
   17:  
   18:         //Entity セット
   19:         public DbSet<ProductwithPrice> ProductwithPrices
   20:  { get; set; }
   21:     }
   22: }

新しいアイテムの追加 : SQL Server への接続文字列を追加

実装の最後に、SQL Server への接続文字列を追加する必要があります。接続文字列は、DBContext の実装で決定したものと、完全に同じ名前である必要があります。そうでない場合には、データベースは生成されませんので、ご注意ください。

App.config ファイルを開き、内容をすべて選択して(Ctrl + A)削除し、下記内容を追加します(ConnectionString)。

    1: <?xml version="1.0" encoding="utf-8"?>
    2: <configuration>
    3:   <connectionStrings>
    4:     <add name="productwithprice_db" 
    5:          providerName="System.Data.SqlClient" 
    6:          connectionString="Data Source=.\sqlexpress;Initial
    7:          Catalog=productdb;Integrated Security=True"/>
    8:   </connectionStrings>
    9: </configuration>

SQL インスタンス名に基づいて、ConnectionString は適宜変更してください。この時点ではデータベースは SQL Server 上には存在していません。もし当該 SQL Server 上に同じ名前のデータベースがある場合、Entity Framework はその既存のデータベースに、テーブルを追加します。

テストプロジェクトの作成とデータの作成

次に、テストをして確認と、データの追加をしておきましょう。コンソールアプリケーションを作成し、正しいモデルが実装されたかを確認します。ファイル->追加->新しいプロジェクトを、順にクリックします。コンソールアプリケーションを選択し、名前を適当に(例:TimeSaleTest)つけてOKをクリックします。

image

先述のTimeSaleプロジェクト同様の手順で、まずは、Entity FrameworkNuGet から取得して追加します。次いで、参照設定を右クリックし、TimeSaleプロジェクトを参照します。 Program.cs ファイルを開いて、下記の通り実装します:

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Linq;
    4: using System.Text;
    5: using System.Threading.Tasks;
    6: using TimeSale;
    7:  
    8: namespace TimeSaleTest
    9: {
   10:     class Program
   11:     {
   12:         static void Main(string[] args)
   13:         {
   14:             TimeSaleAppContext tsc = new TimeSaleAppContext();
   15:  
   16:             var productwithprice = new ProductwithPrice()
   17:             {
   18:                 StoreId = 1,
   19:                 StoreName = "品川店",
   20:                 PriceChangedDate = "2013/1/16 9:00",
   21:                 ProductName = "白菜1個",
   22:                 RetailPrice = "300",
   23:                 ReasonforChangePrice = "通常価格",
   24:                 PriceFixedDate = "2013/1/15 18:00",
   25:                 Contact = "野菜担当 山田"
   26:             };
   27:  
   28:             var productwithprice2 = new ProductwithPrice()
   29:             {
   30:                 StoreId = 2,
   31:                 StoreName = "白金高輪店",
   32:                 PriceChangedDate = "2013/1/16 16:00",
   33:                 ProductName = "ブドウ1房",
   34:                 RetailPrice = "650",
   35:                 ReasonforChangePrice = "夕方のタイムセール",
   36:                 PriceFixedDate = "2013/1/16 12:00",
   37:                 Contact = "店長 鈴木"
   38:             };
   39:  
   40:             var productwithprice3 = new ProductwithPrice()
   41:             {
   42:                 StoreId = 3,
   43:                 StoreName = "五反田店",
   44:                 PriceChangedDate = "2013/1/16 18:00",
   45:                 ProductName = "トマト3個",
   46:                 RetailPrice = "150",
   47:                 ReasonforChangePrice = "タイムセール終了",
   48:                 PriceFixedDate = "2013/1/16 12:00",
   49:                 Contact = "店長 鈴木"
   50:             };
   51:  
   52:             var productwithprice4 = new ProductwithPrice()
   53:             {
   54:                 StoreId = 4,
   55:                 StoreName = "目黒店",
   56:                 PriceChangedDate = "2013/1/17 9:00",
   57:                 ProductName = "玉ねぎ3つ",
   58:                 RetailPrice = "260",
   59:                 ReasonforChangePrice = "木曜セール",
   60:                 PriceFixedDate = "2013/1/16 18:00",
   61:                 Contact = "野菜担当 山田"
   62:             };
   63:  
   64:             var productwithprice5 = new ProductwithPrice()
   65:             {
   66:                 StoreId = 5,
   67:                 StoreName = "渋谷店",
   68:                 PriceChangedDate = "2013/1/17 9:00",
   69:                 ProductName = "人参3本",
   70:                 RetailPrice = "260",
   71:                 ReasonforChangePrice = "木曜セール",
   72:                 PriceFixedDate = "2013/1/16 18:00",
   73:                 Contact = "野菜担当 青木"
   74:             };
   75:  
   76:             var productwithprice6 = new ProductwithPrice()
   77:             {
   78:                 StoreId = 6,
   79:                 StoreName = "川崎店",
   80:                 PriceChangedDate = "2013/1/17 9:00",
   81:                 ProductName = "玉ねぎ3本",
   82:                 RetailPrice = "240",
   83:                 ReasonforChangePrice = "タイムセール終了",
   84:                 PriceFixedDate = "2013/1/16 18:00",
   85:                 Contact = "野菜担当 金子"
   86:             };
   87:  
   88:             var productwithprice7 = new ProductwithPrice()
   89:             {
   90:                 StoreId = 1,
   91:                 StoreName = "品川店",
   92:                 PriceChangedDate = "2013/1/17 9:00",
   93:                 ProductName = "ながのパープル1房",
   94:                 RetailPrice = "700",
   95:                 ReasonforChangePrice = "タイムセール終了",
   96:                 PriceFixedDate = "2013/1/16 18:00",
   97:                 Contact = "野菜担当 藤田"
   98:             };
   99:  
  100:             var productwithprice8 = new ProductwithPrice()
  101:             {
  102:                 StoreId = 5,
  103:                 StoreName = "目黒店",
  104:                 PriceChangedDate = "2013/1/17 9:00",
  105:                 ProductName = "玉ねぎ3本",
  106:                 RetailPrice = "240",
  107:                 ReasonforChangePrice = "木曜タイムセール",
  108:                 PriceFixedDate = "2013/1/16 18:00",
  109:                 Contact = "野菜担当 山田"
  110:             };
  111:  
  112:             var productwithprice9 = new ProductwithPrice()
  113:             {
  114:                 StoreId = 6,
  115:                 StoreName = "川崎店",
  116:                 PriceChangedDate = "2013/1/17 9:00",
  117:                 ProductName = "玉ねぎ3本",
  118:                 RetailPrice = "240",
  119:                 ReasonforChangePrice = "木曜タイムセール",
  120:                 PriceFixedDate = "2013/1/16 18:00",
  121:                 Contact = "野菜担当 金子"
  122:             };
  123:  
  124:             tsc.ProductwithPrices.Add(productwithprice);
  125:             tsc.ProductwithPrices.Add(productwithprice2);
  126:             tsc.ProductwithPrices.Add(productwithprice3);
  127:             tsc.ProductwithPrices.Add(productwithprice4);
  128:             tsc.ProductwithPrices.Add(productwithprice5);
  129:             tsc.ProductwithPrices.Add(productwithprice6);
  130:             tsc.ProductwithPrices.Add(productwithprice7);
  131:             tsc.ProductwithPrices.Add(productwithprice8);
  132:             tsc.ProductwithPrices.Add(productwithprice9);
  133:  
  134:             tsc.SaveChanges();
  135:  
  136:             var pdct = tsc.ProductwithPrices.FirstOrDefault();
  137:             Console.WriteLine("Products from Database");
  138:             Console.WriteLine("9レコード追加しました。最初の商品名は:");
  139:             Console.WriteLine("Products from database Name:{0}",pdct.ProductName);
  140:             Console.Read();
  141:  
  142:         }
  143:     }

TimeSaleTestをスタートアッププロジェクトに設定し、開始ボタンを押して実行します。コンソールアプリケーションが立ち上がり、エンティティが無事にデータベースに作成されたことがわかります。

image

すなわち、productwithprice_db が SQL Server 上に作られ、エンティティおよびデータは、この TimeSaleTest プログラムから作成されているということになります。

SQL Server Management Studio を立ち上げ、ローカルのExpress Edition にログインします。

image

新しいクエリ、をクリックし、下記の通り入力して実行します。

    1: select * from [ProductswithPrice].[dbo].[ProductwithPrices]

すると、下記のような結果が返されます。9件のレコードが確認できます。

image

Code First アプローチは、このようにきわめて強力です。ConnectionString は修正する必要がありますが※、それ以外は Model First アプローチによりコードで書いた通りに稼働します。

※ 接続先を最初から Microsoft Azure SQL Database にしておけば、その上にデータベースが作成されます。

以上です。次は、Web サービス作成とデータ公開、のところを解説します。

鈴木 章太郎