创建数据访问层

作者 :Erik Reitan

本教程系列将介绍如何使用 ASP.NET 4.5 和 Microsoft Visual Studio Express 2013 for Web 生成 ASP.NET Web Forms应用程序。 本教程系列随附了包含 C# 源代码的Visual Studio 2013项目。

本教程介绍如何使用 ASP.NET Web Forms 和 Entity Framework Code First 创建、访问和查看数据库中的数据。 本教程基于上一教程“创建项目”,是 Wingtip 玩具商店教程系列的一部分。 完成本教程后,将生成一组位于项目的 Models 文件夹中的数据访问类。

学习内容:

  • 如何创建数据模型。
  • 如何初始化数据库并设定数据库种子。
  • 如何更新和配置应用程序以支持数据库。

以下是本教程中介绍的功能:

  • 实体框架代码优先
  • LocalDB
  • 数据注释

创建数据模型

实体框架 是 ORM) 框架 (对象关系映射。 它使你能够将关系数据作为对象处理,从而消除了你通常需要编写的大部分数据访问代码。 使用实体框架,可以使用 LINQ 发出查询,然后将数据作为强类型对象进行检索和操作。 LINQ 提供用于查询和更新数据的模式。 使用实体框架可以专注于创建应用程序的其余部分,而不是专注于数据访问基础知识。 本教程系列的后面部分介绍如何使用数据来填充导航和产品查询。

实体框架支持名为 Code First 的开发范例。 Code First 允许使用类定义数据模型。 类属于构造,使用类,可以通过组合其他类型的变量、方法和事件创建自己的自定义类型。 可以将类映射到现有数据库,或使用它们生成数据库。 在本教程中,你将通过编写数据模型类来创建数据模型。 然后,让实体框架从这些新类动态创建数据库。

首先创建实体类,这些实体类定义Web Forms应用程序的数据模型。 然后,将创建一个上下文类,用于管理实体类并提供对数据库的数据访问。 还将创建一个初始值设定项类,用于填充数据库。

实体框架和引用

默认情况下,使用 Web Forms 模板创建新的ASP.NET Web 应用程序时,会包含实体框架。 实体框架可以作为 NuGet 包进行安装、卸载和更新。

此 NuGet 包在项目中包含以下 运行时 程序集:

  • EntityFramework.dll – 实体框架使用的所有通用运行时代码
  • EntityFramework.SqlServer.dll – 适用于实体框架的 Microsoft SQL Server提供程序

实体类

创建用于定义数据架构的类称为实体类。 如果你不熟悉数据库设计,请将实体类视为数据库的表定义。 类中的每个属性指定数据库表中的一列。 这些类在面向对象的代码和数据库的关系表结构之间提供一个轻型的对象关系接口。

在本教程中,首先添加表示产品和类别架构的简单实体类。 products 类将包含每个产品的定义。 产品类的每个成员的名称将为 ProductID、、ProductNameDescriptionImagePathUnitPriceCategoryIDCategory。 类别类将包含产品可以属于的每个类别的定义,例如 Car、Boat 或 Plane。 类别类的每个成员的名称将为 CategoryIDCategoryNameDescriptionProducts。 每个产品都属于其中一个类别。 这些实体类将添加到项目的现有 Models 文件夹中。

  1. “解决方案资源管理器”中,右键单击“模型”文件夹,然后选择“添加 ->新建项”。

    “解决方案资源管理器”窗口的屏幕截图,其中突出显示了“模型”文件夹,并选中了“添加”和“新建项”下拉菜单。

    随即出现“添加新项”对话框。

  2. 在左侧“已安装”窗格中的“Visual C#”下,选择“代码”。

    “添加新项”窗口的屏幕截图,其中左侧显示了“已安装”窗格,其中打开了 Visual C# 并选择了“代码”。

  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 类) ),并提供对数据库的数据访问。

此过程将新的 C# 上下文类添加到 Models 文件夹。

  1. 右键单击“ 模型” 文件夹,然后选择“ 添加 ->新建项”。
    随即出现“添加新项”对话框。

  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; }
        }
    }
    

此代码添加 System.Data.Entity 命名空间,以便你可以访问 Entity Framework 的所有核心功能,其中包括通过使用强类型化对象来查询、插入、更新和删除数据的功能。

ProductContext类表示 Entity Framework 产品数据库上下文,该上下文处理数据库中的类实例的提取、存储和更新Product。 类 ProductContext 派生自 DbContext 实体框架提供的基类。

初始值设定项类

首次使用上下文时,需要运行一些自定义逻辑来初始化数据库。 这将允许将种子数据添加到数据库,以便可以立即显示产品和类别。

此过程将新的 C# 初始值设定项类添加到 Models 文件夹。

  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 实体类进行更改,则不会使用种子数据重新初始化数据库。

注意

如果希望在每次运行应用程序时重新创建数据库,则可以使用 DropCreateDatabaseAlways 类而不是 DropCreateDatabaseIfModelChanges 类。 但是,对于本教程系列,请使用 DropCreateDatabaseIfModelChanges 类。

在本教程中,此时将有一个 Models 文件夹,其中包含四个新类和一个默认类:

创建数据访问层 - Models 文件夹

将应用程序配置为使用数据模型

创建表示数据的类后,必须将应用程序配置为使用这些类。 在 Global.asax 文件中,添加初始化模型的代码。 在 Web.config 文件中添加信息,告知应用程序你将使用哪个数据库来存储由新数据类表示的数据。 Global.asax 文件可用于处理应用程序事件或方法。 使用Web.config 文件可以控制 ASP.NET Web 应用程序的配置。

更新 Global.asax 文件

若要在应用程序启动时初始化数据模型,需要更新 Application_StartGlobal.asax.cs 文件中的处理程序。

注意

在解决方案资源管理器中,可以选择 Global.asax 文件或 Global.asax.cs 文件来编辑 Global.asax.cs 文件。

  1. 将以下以黄色突出显示的代码添加到 Application_StartGlobal.asax.cs 文件中的 方法。

    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());
            }
        }
    }
    

注意

在浏览器中查看本教程系列时,浏览器必须支持 HTML5 才能查看以黄色突出显示的代码。

如上述代码所示,应用程序启动时,应用程序指定在首次访问数据时将运行的初始值设定项。 访问 Database 对象和 ProductDatabaseInitializer 对象需要另外两个命名空间。

修改Web.Config文件

尽管使用种子数据填充数据库时,Entity Framework Code First 会在默认位置为你生成一个数据库,但向应用程序添加你自己的连接信息可让你控制数据库位置。 使用项目根目录中应用程序的 Web.config 文件中的连接字符串指定此数据库连接。 通过添加新的连接字符串,可以将数据库 (wingtiptoys.mdf) 的位置定向到应用程序的数据目录中, (App_Data) ,而不是默认位置。 进行此更改后,可以在本教程后面查找和检查数据库文件。

  1. 解决方案资源管理器 中,找到并打开 Web.config 文件。

  2. 将以黄色突出显示的连接字符串添加到 <connectionStrings>Web.config 文件的 部分,如下所示:

    <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”。
    将显示 “输出 ”窗口,如果一切顺利,则会看到 成功 消息。

    创建数据访问层 - 输出窗口

如果遇到错误,请重新检查上述步骤。 “输出”窗口中的信息将指示哪个文件有问题,以及文件中需要更改的位置。 此信息可让你确定需要在项目中查看和修复上述步骤的哪一部分。

总结

在系列教程中,你已创建数据模型,并添加了将用于初始化数据库并设定其种子的代码。 你还已将应用程序配置为在运行应用程序时使用数据模型。

在下一教程中,你将更新 UI、添加导航并从数据库检索数据。 这将导致根据在本教程中创建的实体类自动创建数据库。

其他资源

实体框架概述
ADO.NET 实体框架初学者指南
使用实体框架代码优先关系进行代码优先开发 Fluent API
Code First 数据批注
实体框架的生产力改进