对新数据库使用 Code First

此视频和分步演练介绍了面向新数据库的 Code First 开发。 此方案包括面向不存在且 Code First 将创建的数据库,或面向 Code First 将向其中添加新表的空数据库。 Code First 允许你使用 C# 或 VB.Net 类定义模型。 可以选择使用类和属性上的特性或使用 Fluent API 执行其他配置。

观看视频

此视频介绍了面向新数据库的 Code First 开发。 此方案包括面向不存在且 Code First 将创建的数据库,或面向 Code First 将向其中添加新表的空数据库。 Code First 允许你使用 C# 或 VB.Net 类定义模型。 可以选择使用类和属性上的特性或使用 Fluent API 执行其他配置。

主讲人Rowan Miller

视频WMV | MP4 | WMV (ZIP)

先决条件

至少需要安装 Visual Studio 2010 或 Visual Studio 2012 才能完成本演练。

如果使用 Visual Studio 2010,还需要安装 NuGet

1. 创建应用程序

为简单起见,我们将构建一个使用 Code First 执行数据访问的基本控制台应用程序。

  • 打开 Visual Studio
  • “文件”->“新建”->“项目…”
  • 从左侧菜单中选择“Windows”并选择“控制台应用程序”
  • 输入 CodeFirstNewDatabaseSample 作为名称
  • 选择“确定”

2. 创建模型

让我们使用类定义一个非常简单的模型。 我们只是在 Program.cs 文件中定义它们,但在实际应用程序中,你可以将类拆分为单独的文件,也可能拆分为单独的项目。

在 Program.cs 中的 Program 类定义下方添加以下两个类。

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
}

你会注意到我们将两个导航属性(Blog.Posts 和 Post.Blog)设为虚拟。 这将启用实体框架的延迟加载功能。 延迟加载意味着当你尝试访问这些属性时,将自动从数据库加载这些属性的内容。

3. 创建上下文

现在,可以定义一个派生上下文,该上下文表示与数据库的会话,使我们能够查询和保存数据。 我们定义了一个派生自 System.Data.Entity.DbContext 的上下文,并为模型中的每个类公开一个类型化 DbSet<TEntity>。

我们现在开始使用实体框架中的类型,因此需要添加 EntityFramework NuGet 包。

  • 项目 -> 管理 NuGet 包… 注意:如果没有“管理 NuGet 包...”选项,应安装最新版本的 NuGet
  • 选择“联机”选项卡
  • 选择 EntityFramework
  • 单击“安装”

在 Program.cs 的顶部为 System.Data.Entity 添加 using 语句。

using System.Data.Entity;

在 Program.cs 中的 Post 类下面添加以下派生上下文。

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

下面是 Program.cs 现在应该包含的内容的完整列表。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace CodeFirstNewDatabaseSample
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }

        public virtual List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public virtual Blog Blog { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
    }
}

这就是我们开始存储和检索数据所需的全部代码。 很显然,后台执行了许多操作,我们稍后来看一下,但让我们先来了解实际运行效果。

4.读取和写入数据

在 Program.cs 中实现 Main 方法,如下所示。 此代码创建一个新的上下文实例,然后使用它来插入新的 Blog。 接着,它使用 LINQ 查询从数据库中检索按标题字母顺序排序的所有 Blog。

class Program
{
    static void Main(string[] args)
    {
        using (var db = new BloggingContext())
        {
            // Create and save a new Blog
            Console.Write("Enter a name for a new Blog: ");
            var name = Console.ReadLine();

            var blog = new Blog { Name = name };
            db.Blogs.Add(blog);
            db.SaveChanges();

            // Display all Blogs from the database
            var query = from b in db.Blogs
                        orderby b.Name
                        select b;

            Console.WriteLine("All blogs in the database:");
            foreach (var item in query)
            {
                Console.WriteLine(item.Name);
            }

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

现在,可运行应用程序并对其进行测试。

Enter a name for a new Blog: ADO.NET Blog
All blogs in the database:
ADO.NET Blog
Press any key to exit...

我的数据在哪里?

按照约定,DbContext 已经为你创建了一个数据库。

  • 如果本地 SQL Express 实例可用(默认情况下随 Visual Studio 2010 一起安装),则 Code First 已在该实例上创建了数据库
  • 如果 SQL Express 不可用,则 Code First 将尝试使用 LocalDB(默认情况下随 Visual Studio 2012 一起安装)
  • 数据库以派生上下文的完全限定名称命名,在本例中为 CodeFirstNewDatabaseSample.BloggingContext

这些只是默认约定,有多种方法可以更改 Code First 使用的数据库,有关详细信息,请参见 DbContext 如何发现模型和数据库连接主题。 你可以使用 Visual Studio 中的服务器资源管理器连接到此数据库

  • “视图”->“服务器资源管理器”

  • 右键单击“数据连接”并选择“添加连接...”

  • 如果尚未从服务器资源管理器连接到数据库,则需要选择 Microsoft SQL Server 作为数据源

    Select Data Source

  • 连接 LocalDB 或 SQL Express,具体取决于安装的是哪一项

现在我们可以检查 Code First 创建的架构。

Schema Initial

DbContext 通过查看我们定义的 DbSet 属性来确定要在模型中包含哪些类。 然后使用默认的 Code First 约定集来确定表名和列名、确定数据类型、查找主键等。 稍后在本演练中,我们将了解如何覆盖这些约定。

5. 处理模型更改

现在可以对模型进行一些更改,当我们进行这些更改时,还需要更新数据库架构。 为此,我们将使用一项称为 Code First 迁移(简称迁移)的功能。

借助迁移功能,我们可以拥有一组有序的步骤,这些步骤介绍如何升级(和降级)数据库架构。 每个步骤(称为迁移)都包含一些描述要应用的更改的代码。 

第一步是为 BloggingContext 启用 Code First 迁移。

  • “工具”->“库包管理器”->“包管理器控制台”

  • 在包管理器控制台中运行 Enable-Migrations 命令

  • 已将新的 Migrations 文件夹添加到项目中,其中包含两项:

    • Configuration.cs - 此文件包含 Migrations 用于迁移 BloggingContext 的设置。 对于本演练,我们不需要更改任何内容,但你可以在此处指定种子数据、为其他数据库注册提供程序、更改将在其中生成迁移的命名空间等。
    • <timestamp>_InitialCreate.cs - 这是你的第一次迁移,它代表已应用于数据库的更改,将其从空数据库变为包含 Blogs 和 Posts 表的数据库。 虽然我们让 Code First 自动为我们创建这些表,但既然我们选择了迁移,它们便已转换为迁移。 Code First 还在我们的本地数据库中记录了此迁移已应用。 文件名上的时间戳用于排序目的。

    现在让我们对模型进行更改,向 Blog 类添加一个 Url 属性:

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }

    public virtual List<Post> Posts { get; set; }
}
  • 在包管理器控制台中运行 Add-Migration AddUrl 命令。 Add-Migration 命令检查自上次迁移以来的更改,并使用找到的任何更改为新的迁移搭建基架。 我们可以为迁移指定一个名称;在本例中,我们将迁移称为“AddUrl”。 已搭建基架的代码表示我们需要向 dbo.Blogs 表添加一个 Url 列,该列可以保存字符串数据。 如果需要,可以对已搭建基架的代码进行编辑,但在本例中不需要。
namespace CodeFirstNewDatabaseSample.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class AddUrl : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.Blogs", "Url", c => c.String());
        }

        public override void Down()
        {
            DropColumn("dbo.Blogs", "Url");
        }
    }
}
  • 在包管理器控制台中运行 Update-Database 命令。 此命令将对数据库应用任意挂起的迁移。 InitialCreate 迁移已经应用,因此迁移将只应用新的 AddUrl 迁移。 提示:调用 Update-Database 时,可以使用 –Verbose 开关查看针对数据库执行的 SQL

新的 Url 列现在已添加到数据库中的 Blogs 表中:

Schema With Url

6. 数据注释

到目前为止,我们只是让 EF 使用其默认约定发现模型,但有时我们的类不遵循约定,我们需要能够执行进一步的配置。 有两个选项可供选择;我们将在本节中了解数据注释,然后在下一节中了解 Fluent API。

  • 让我们向模型添加一个用户类
public class User
{
    public string Username { get; set; }
    public string DisplayName { get; set; }
}
  • 我们还需要向派生上下文添加一个集
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<User> Users { get; set; }
}
  • 如果我们尝试添加迁移,会收到一个错误,指示“EntityType‘User’未定义键。请为该 EntityType 定义键。”,因为 EF 无法判断 Username 应为 User 的主键
  • 现在我们将使用数据注释,因此需要在 Program.cs 的顶部添加 using 语句
using System.ComponentModel.DataAnnotations;
  • 现在,请对 Username 属性进行批注,以标识它是主键
public class User
{
    [Key]
    public string Username { get; set; }
    public string DisplayName { get; set; }
}
  • 使用 Add-Migration AddUser 命令为迁移搭建基架以将这些更改应用到数据库
  • 运行 Update-Database 命令以将新迁移应用到数据库

新表现已添加到数据库中:

Schema With Users

EF 支持的完整注释列表如下:

7. Fluent API

在上一节中,我们探讨了使用数据注释来补充或覆盖按约定检测到的内容。 另一种配置模型的方法是通过 Code First Fluent API。

大多数模型配置可以使用简单的数据注释来完成。 Fluent API 是一种更高级的指定模型配置的方法,该方法涵盖了数据注释可以执行的所有操作,还包含一些数据注释无法实现的更高级配置。 数据注释和 Fluent API 可以一起使用。

若要访问 Fluent API,可以重写 DbContext 中的 OnModelCreating 方法。 假设我们想将存储 User.DisplayName 的列重命名为 display_name。

  • 使用以下代码重写 BloggingContext 上的 OnModelCreating 方法
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .Property(u => u.DisplayName)
            .HasColumnName("display_name");
    }
}
  • 使用 Add-Migration ChangeDisplayName 命令为迁移搭建基架以将这些更改应用到数据库
  • 运行 Update-Database 命令以将新迁移应用到数据库

DisplayName 列现已重命名为 display_name:

Schema With Display Name Renamed

总结

在本演练中,我们探讨了使用新数据库的 Code First 开发。 我们使用类定义了一个模型,然后使用该模型创建数据库并存储和检索数据。 创建数据库后,我们使用 Code First 迁移来随着模型的发展更改架构。 我们还了解了如何使用数据注释和 Fluent API 配置模型。