创建数据访问层

作者 :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 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应用程序的数据模型的实体类。 然后,你将创建一个上下文类,用于管理实体类并提供对数据库的数据访问。 还将创建一个初始值设定项类,用于填充数据库。

实体框架和引用

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

此 NuGet 包包括项目中的以下 运行时 程序集:

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

实体类

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

在本教程中,你将首先添加表示产品和类别架构的简单实体类。 产品类将包含每个产品的定义。 产品类的每个成员的名称将为ProductID、、ProductName、、DescriptionImagePathUnitPriceCategoryIDCategory。 类别类将包含产品可以属于的每个类别的定义,例如汽车、船或平面。 类别类的每个成员的名称将为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 创建另一个类,将新类 类别命名.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 Entity Framework 提供的基类。

Initializer 类

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

此过程将新的 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 实现来识别模型 (架构) 在重置种子数据之前是否已更改。 如果未对 Category 实体类和 Product 实体类进行更改,则不会使用种子数据重新初始化数据库。

注意

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

在本教程中,你将有一个 模型 文件夹,其中包含四个新类和一个默认类:

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

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

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

更新 Global.asax 文件

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

注意

在解决方案资源管理器中,可以选择 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 才能在浏览器中查看本教程系列时以黄色突出显示的代码。

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

修改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”。
    此时会显示 “输出 ”窗口,如果一切正常,则会看到一条 成功的 消息。

    创建数据访问层 - 输出 Windows

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

总结

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

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

其他资源

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