データ アクセス層の作成

作成者: Erik Reitan

このチュートリアル シリーズでは、ASP.NET 4.5 と Microsoft Visual Studio Express 2013 for Web を使った ASP.NET Web Forms アプリケーション構築の基本について説明します。 Visual Studio 2013 の C# ソース コードを含むプロジェクトは、このチュートリアル シリーズへの関連付けに使用できます。

このチュートリアルでは、ASP.NET Web Forms と Entity Framework Code First を使って、データベースからデータを作成、アクセス、確認する方法について説明します。 このチュートリアルは、前のチュートリアル「プロジェクトの作成」に基づいており、Wingtip Toy Store チュートリアル シリーズの一部です。 このチュートリアルを完了すると、プロジェクトの Models フォルダーにあるデータ アクセス クラスのグループが作成されます。

ここでは、次の内容について学習します。

  • データ モデルを作成する方法。
  • データベースを初期化してシード処理する方法。
  • データベースをサポートするようにアプリケーションを更新して構成する方法。

このチュートリアルで紹介する機能:

  • Entity Framework Code First
  • LocalDB
  • データの注釈

データ モデルを作成する

Entity Framework は、オブジェクト/リレーショナル マッピング (ORM) フレームワークです。 リレーショナル データをオブジェクトとして操作できるため、通常は記述する必要があるデータ アクセス コードの大部分を排除できます。 Entity Framework を使用すると、LINQ を使用してクエリを発行し、データを厳密に型指定されたオブジェクトとして取得、操作できます。 LINQ には、データの照会と更新のパターンが用意されています。 Entity Framework を使用すると、データ アクセスの基礎に焦点を当てるのではなく、アプリケーションの残りの部分を作成することに集中できます。 このチュートリアル シリーズの後半では、データを使用してナビゲーションと製品のクエリを設定する方法について説明します。

Entity Framework では、Code First と呼ばれる開発パラダイムがサポートされています。 Code First により、クラスを使用してデータ モデルを定義できます。 クラスとは、他の型、メソッド、およびイベントの変数をまとめてグループ化することで独自のカスタム型を作成できる構成要素です。 既存のデータベースにクラスをマップすることも、それらを使用してデータベースを生成することもできます。 このチュートリアルでは、データ モデル クラスを記述してデータ モデルを作成します。 次に、Entity Framework でこれらの新しいクラスからすぐにデータベースを作成できるようにします。

まず、Web Forms アプリケーションのデータ モデルを定義するエンティティ クラスを作成します。 次に、エンティティ クラスを管理し、データベースへのデータ アクセスを提供するコンテキスト クラスを作成します。 また、データベースの設定に使用する初期化子クラスも作成します。

Entity Framework と参照

既定では、Entity Framework は、Web Forms テンプレートを使用して新しい ASP.NET Web アプリケーションを作成するときに含まれます。 Entity Framework は、NuGet パッケージとしてインストール、アンインストール、更新できます。

この NuGet パッケージには、プロジェクト内に次のランタイム アセンブリが含まれています。

  • EntityFramework.dll - Entity Framework で使用されるすべての一般的なランタイム コード
  • EntityFramework.SqlServer.dll - Entity Framework 用 Microsoft SQL Server プロバイダー

エンティティ クラス

データのスキーマを定義するために作成するクラスは、エンティティ クラスと呼ばれます。 データベースの設計を初めて使用する場合は、エンティティ クラスをデータベースのテーブル定義と考えてください。 クラスの各プロパティは、データベースのテーブル内の列を指定します。 これらのクラスは、オブジェクト指向コードとデータベースのリレーショナル テーブル構造の間に、軽量のオブジェクト リレーショナル インターフェイスを提供します。

このチュートリアルでは、まず、製品とカテゴリのスキーマを表す単純なエンティティ クラスを追加します。 Product クラスには、各製品の定義が含まれます。 Product クラスの各メンバーの名前は、ProductIDProductNameDescriptionImagePathUnitPriceCategoryIDCategory になります。 Category クラスには、製品が属する各カテゴリ (車、ボート、飛行機など) の定義が含まれます。 Category クラスの各メンバーの名前は、CategoryIDCategoryNameDescriptionProducts になります。 各製品は、いずれかのカテゴリに属します。 これらのエンティティ クラスは、プロジェクトの既存の Models フォルダーに追加されます。

  1. ソリューション エクスプローラーModels フォルダーを右クリックし、[追加] ->[新規項目] の順に選択します。

    Screenshot of the Solution Explorer window with the Models folder highlighted and the dropdown menus Add and New Item selected.

    [新しい項目の追加] ダイアログ ボックスが表示されます。

  2. 左側にある [インストール済み] ペインで、[Visual C#][コード] を選択します。

    Screenshot of the Add New Item window showing the Installed pane on the left with Visual C# open and Code selected.

  3. 中央のペインから [クラス] を選択し、この新しいクラスに Product.cs という名前を付けます。

  4. 追加をクリックします。
    新しいクラス ファイルがエディターに表示されます。

  5. 既定のコードを以下のコードに置き換えます。

    using System.ComponentModel.DataAnnotations;
    
    namespace WingtipToys.Models
    {
        public class Product
        {
            [ScaffoldColumn(false)]
            public int ProductID { get; set; }
    
            [Required, StringLength(100), Display(Name = "Name")]
            public string ProductName { get; set; }
    
            [Required, StringLength(10000), Display(Name = "Product Description"), DataType(DataType.MultilineText)]
            public string Description { get; set; }
    
            public string ImagePath { get; set; }
    
            [Display(Name = "Price")]
            public double? UnitPrice { get; set; }
    
            public int? CategoryID { get; set; }
    
            public virtual Category Category { get; set; }
        }
    }
    
  6. 手順 1 から 4 を繰り返して別のクラスを作成します。ただし、新しいクラスに Category.cs という名前を付け、既定のコードを次のコードに置き換えます。

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace WingtipToys.Models
    {
        public class Category
        {
            [ScaffoldColumn(false)]
            public int CategoryID { get; set; }
    
            [Required, StringLength(100), Display(Name = "Name")]
            public string CategoryName { get; set; }
    
            [Display(Name = "Product Description")]
            public string Description { get; set; }
    
            public virtual ICollection<Product> Products { get; set; }
        }
    }
    

前述のように、Category クラスはアプリケーションが販売するように設計されている製品の種類 ("車"、"ボート"、"ロケット" など) を表し、Product クラスはデータベース内の個々の製品 (おもちゃ) を表します。 Product オブジェクトの各インスタンスはリレーショナル データベース テーブルの各行に相当し、Product クラスの各プロパティは、リレーショナル データベース テーブルの各列に対応します。 データベースに格納されている製品データについては、このチュートリアルで後ほど説明します。

データの注釈

クラスの特定のメンバーに、メンバーに関する詳細を指定する属性 ([ScaffoldColumn(false)] など) があることにお気付きかもしれません。 これらはデータ注釈です。 データ注釈属性は、そのメンバーについて、どのようにユーザー入力を検証し、書式設定を指定し、データベース作成時にモデル化するかを記述できます。

Context クラス

データ アクセス用のクラスの使用を開始するには、コンテキスト クラスを定義する必要があります。 前述のように、コンテキスト クラスはエンティティ クラス (Product クラスや Category クラスなど) を管理し、データベースへのデータ アクセスを提供します。

この手順では、Models フォルダーに新しい C# コンテキスト クラスを追加します。

  1. Models フォルダーを右クリックし、[追加] ->[新規項目] の順に選択します。
    [新しい項目の追加] ダイアログ ボックスが表示されます。

  2. 中央のペインから [クラス] を選択し、ProductContext.cs という名前を付けて [追加] をクリックします。

  3. クラスに含まれる既定のコードを次のコードに置き換えます。

    using System.Data.Entity;
    namespace WingtipToys.Models
    {
        public class ProductContext : DbContext
        {
            public ProductContext() : base("WingtipToys")
            {
            }
            public DbSet<Category> Categories { get; set; }
            public DbSet<Product> Products { get; set; }
        }
    }
    

このコードは、Entity Framework のすべてのコア機能にアクセスできるように System.Data.Entity 名前空間を追加します。これには、厳密に型指定されたオブジェクトを操作してデータのクエリ、挿入、更新、削除を行う機能が含まれます。

ProductContext クラスは Entity Framework 製品データベース コンテキストを表し、データベース内の Product クラス インスタンスのフェッチ、格納、更新を処理します。 ProductContext クラスは、Entity Framework が提供する DbContext 基底クラスから派生しています。

初期化子クラス

コンテキストが初めて使用されるときにデータベースを初期化するには、いくつかのカスタム ロジックを実行する必要があります。 これにより、シード データをデータベースに追加して、製品とカテゴリをすぐに表示できるようになります。

このプロシージャは、Models フォルダーに新しい C# 初期化子クラスを追加します。

  1. Models フォルダーに別の Class を作成し、ProductDatabaseInitializer.cs という名前を付けます。

  2. クラスに含まれる既定のコードを次のコードに置き換えます。

    using System.Collections.Generic;
    using System.Data.Entity;
    
    namespace WingtipToys.Models
    {
      public class ProductDatabaseInitializer : DropCreateDatabaseIfModelChanges<ProductContext>
      {
        protected override void Seed(ProductContext context)
        {
          GetCategories().ForEach(c => context.Categories.Add(c));
          GetProducts().ForEach(p => context.Products.Add(p));
        }
    
        private static List<Category> GetCategories()
        {
          var categories = new List<Category> {
                    new Category
                    {
                        CategoryID = 1,
                        CategoryName = "Cars"
                    },
                    new Category
                    {
                        CategoryID = 2,
                        CategoryName = "Planes"
                    },
                    new Category
                    {
                        CategoryID = 3,
                        CategoryName = "Trucks"
                    },
                    new Category
                    {
                        CategoryID = 4,
                        CategoryName = "Boats"
                    },
                    new Category
                    {
                        CategoryID = 5,
                        CategoryName = "Rockets"
                    },
                };
    
          return categories;
        }
    
        private static List<Product> GetProducts()
        {
          var products = new List<Product> {
                    new Product
                    {
                        ProductID = 1,
                        ProductName = "Convertible Car",
                        Description = "This convertible car is fast! The engine is powered by a neutrino based battery (not included)." + 
                                      "Power it up and let it go!", 
                        ImagePath="carconvert.png",
                        UnitPrice = 22.50,
                        CategoryID = 1
                   },
                    new Product 
                    {
                        ProductID = 2,
                        ProductName = "Old-time Car",
                        Description = "There's nothing old about this toy car, except it's looks. Compatible with other old toy cars.",
                        ImagePath="carearly.png",
                        UnitPrice = 15.95,
                         CategoryID = 1
                   },
                    new Product
                    {
                        ProductID = 3,
                        ProductName = "Fast Car",
                        Description = "Yes this car is fast, but it also floats in water.",
                        ImagePath="carfast.png",
                        UnitPrice = 32.99,
                        CategoryID = 1
                    },
                    new Product
                    {
                        ProductID = 4,
                        ProductName = "Super Fast Car",
                        Description = "Use this super fast car to entertain guests. Lights and doors work!",
                        ImagePath="carfaster.png",
                        UnitPrice = 8.95,
                        CategoryID = 1
                    },
                    new Product
                    {
                        ProductID = 5,
                        ProductName = "Old Style Racer",
                        Description = "This old style racer can fly (with user assistance). Gravity controls flight duration." + 
                                      "No batteries required.",
                        ImagePath="carracer.png",
                        UnitPrice = 34.95,
                        CategoryID = 1
                    },
                    new Product
                    {
                        ProductID = 6,
                        ProductName = "Ace Plane",
                        Description = "Authentic airplane toy. Features realistic color and details.",
                        ImagePath="planeace.png",
                        UnitPrice = 95.00,
                        CategoryID = 2
                    },
                    new Product
                    {
                        ProductID = 7,
                        ProductName = "Glider",
                        Description = "This fun glider is made from real balsa wood. Some assembly required.",
                        ImagePath="planeglider.png",
                        UnitPrice = 4.95,
                        CategoryID = 2
                    },
                    new Product
                    {
                        ProductID = 8,
                        ProductName = "Paper Plane",
                        Description = "This paper plane is like no other paper plane. Some folding required.",
                        ImagePath="planepaper.png",
                        UnitPrice = 2.95,
                        CategoryID = 2
                    },
                    new Product
                    {
                        ProductID = 9,
                        ProductName = "Propeller Plane",
                        Description = "Rubber band powered plane features two wheels.",
                        ImagePath="planeprop.png",
                        UnitPrice = 32.95,
                        CategoryID = 2
                    },
                    new Product
                    {
                        ProductID = 10,
                        ProductName = "Early Truck",
                        Description = "This toy truck has a real gas powered engine. Requires regular tune ups.",
                        ImagePath="truckearly.png",
                        UnitPrice = 15.00,
                        CategoryID = 3
                    },
                    new Product
                    {
                        ProductID = 11,
                        ProductName = "Fire Truck",
                        Description = "You will have endless fun with this one quarter sized fire truck.",
                        ImagePath="truckfire.png",
                        UnitPrice = 26.00,
                        CategoryID = 3
                    },
                    new Product
                    {
                        ProductID = 12,
                        ProductName = "Big Truck",
                        Description = "This fun toy truck can be used to tow other trucks that are not as big.",
                        ImagePath="truckbig.png",
                        UnitPrice = 29.00,
                        CategoryID = 3
                    },
                    new Product
                    {
                        ProductID = 13,
                        ProductName = "Big Ship",
                        Description = "Is it a boat or a ship. Let this floating vehicle decide by using its " + 
                                      "artifically intelligent computer brain!",
                        ImagePath="boatbig.png",
                        UnitPrice = 95.00,
                        CategoryID = 4
                    },
                    new Product
                    {
                        ProductID = 14,
                        ProductName = "Paper Boat",
                        Description = "Floating fun for all! This toy boat can be assembled in seconds. Floats for minutes!" + 
                                      "Some folding required.",
                        ImagePath="boatpaper.png",
                        UnitPrice = 4.95,
                        CategoryID = 4
                    },
                    new Product
                    {
                        ProductID = 15,
                        ProductName = "Sail Boat",
                        Description = "Put this fun toy sail boat in the water and let it go!",
                        ImagePath="boatsail.png",
                        UnitPrice = 42.95,
                        CategoryID = 4
                    },
                    new Product
                    {
                        ProductID = 16,
                        ProductName = "Rocket",
                        Description = "This fun rocket will travel up to a height of 200 feet.",
                        ImagePath="rocket.png",
                        UnitPrice = 122.95,
                        CategoryID = 5
                    }
                };
    
          return products;
        }
      }
    }
    

上記のコードからわかるように、データベースが作成され、初期化されると、Seed プロパティがオーバーライドされて設定されます。 Seed プロパティを設定すると、カテゴリと製品の値を使用してデータベースが設定されます。 データベースの作成後に上記のコードを変更してシード データを更新しようとしても、Web アプリケーションの実行時に更新内容は表示されません。 その理由は、上記のコードが、シード データをリセットする前に、DropCreateDatabaseIfModelChanges クラスの実装を使用してモデル (スキーマ) が変更されたかどうかを認識するためです。 CategoryProduct のエンティティ クラスに変更が加えられない場合、データベースはシード データで再初期化されません。

Note

アプリケーションを実行するたびにデータベースを再作成する場合は、DropCreateDatabaseIfModelChanges クラスの代わりに DropCreateDatabaseAlways クラスを使用できます。 ただし、このチュートリアル シリーズでは DropCreateDatabaseIfModelChanges クラスを使用します。

このチュートリアルのこの時点で、4 つの新しいクラスと 1 つの既定のクラスを含む Models フォルダーが作成されます。

Create the Data Access Layer - Models Folder

データ モデルを使用するようにアプリケーションを構成する

データを表すクラスを作成したので、クラスを使用するアプリケーションを構成する必要があります。 Global.asax ファイルに、モデルを初期化するコードを追加します。 Web.config ファイルに、新しいデータ クラスで表すデータをどのデータベースを使用して格納するかをアプリケーションに通知する情報を追加します。 Global.asax ファイルは、アプリケーション イベントまたはメソッドの処理に使用できます。 Web.config ファイルを使用すると、ASP.NET Web アプリケーションの構成を制御できます。

Global.asax ファイルを更新する

アプリケーションの起動時にデータ モデルを初期化するには、Global.asax.cs ファイル内の Application_Start ハンドラーを更新します。

Note

ソリューション エクスプローラーでは、Global.asax ファイルまたは Global.asax.cs ファイルのいずれかを選択して、Global.asax.cs ファイルを編集できます。

  1. Global.asax.cs ファイル内の Application_Start メソッドに、黄色で強調表示されている次のコードを追加します。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Optimization;
    using System.Web.Routing;
    using System.Web.Security;
    using System.Web.SessionState;
    using System.Data.Entity;
    using WingtipToys.Models;
    
    namespace WingtipToys
    {
        public class Global : HttpApplication
        {
            void Application_Start(object sender, EventArgs e)
            {
                // Code that runs on application startup
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
    
                // Initialize the product database.
                Database.SetInitializer(new ProductDatabaseInitializer());
            }
        }
    }
    

Note

ブラウザーでこのチュートリアル シリーズを表示している場合、黄色で強調表示されたコードを表示するには、ブラウザーが HTML5 をサポートしている必要があります。

上記のコードに示すように、アプリケーションの起動時に、データに初めてアクセスするときに実行される初期化子がアプリケーションによって指定されます。 Database オブジェクトと ProductDatabaseInitializer オブジェクトにアクセスするには、2 つの追加の名前空間が必要です。

Web.config ファイルを変更する

Entity Framework Code First は、データベースにシード データが設定されるときに、既定の場所にデータベースを生成しますが、独自の接続情報をアプリケーションに追加すると、データベースの場所を制御できます。 このデータベース接続は、プロジェクトのルートにある、アプリケーションの Web.config ファイルの接続文字列を使用して指定します。 新しい接続文字列を追加すると、データベース (wingtiptoys.mdf) の場所を、既定の場所ではなく、アプリケーションのデータ ディレクトリ (App_Data) にビルドするように指示できます。 この変更を行うと、このチュートリアルの後半で、データベース ファイルを見つけて調べることができます。

  1. ソリューション エクスプローラーで、Web.config ファイルを見つけて開きます。

  2. 次のように、黄色で強調表示されている接続文字列を Web.config ファイルの <connectionStrings> セクションに追加します。

    <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-WingtipToys-20131119102907.mdf;Initial Catalog=aspnet-WingtipToys-20131119102907;Integrated Security=True"
    providerName="System.Data.SqlClient" />
    <add name="WingtipToys"
    connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\wingtiptoys.mdf;Integrated Security=True"
    providerName="System.Data.SqlClient" />
    </connectionStrings>
    

アプリケーションを初めて実行すると、接続文字列で指定された場所にデータベースがビルドされます。 ただし、アプリケーションを実行する前に、まずはビルドしましょう。

アプリケーションのビルド

すべてのクラスと Web アプリケーションへの変更が機能することを確認するには、アプリケーションをビルドする必要があります。

  1. [デバッグ] メニューの [WingtipToys のビルド] を選択します。
    [出力] ウィンドウが表示され、すべてが正常に完了した場合は、成功したことを示すメッセージが表示されます。

    Create the Data Access Layer - Output Windows

エラーが発生した場合は、上記の手順をもう一度確認してください。 [出力] ウィンドウの情報は、どのファイルに問題があり、ファイル内のどの部分に変更が必要かを示します。 この情報を使用すると、上記の手順のどの部分を確認し、プロジェクトで修正する必要があるかを判断できます。

まとめ

データ モデルを作成したこのシリーズのチュートリアルでは、データベースの初期化とシード処理に使用するコードを追加しました。 また、アプリケーションの実行時にデータ モデルを使用するようにアプリケーションを構成しました。

次のチュートリアルでは、UI の更新、ナビゲーションの追加、データベースからのデータの取得を行います。 これにより、このチュートリアルで作成したエンティティ クラスに基づいてデータベースが自動的に作成されます。

その他のリソース

Entity Framework の概要
ADO.NET Entity Framework の初心者向けガイド
Entity Framework を使用した Code First 開発Code First Relationships Fluent API
Code First Data Annotations (Code First のデータ注釈)
Entity Framework の生産性の向上