Entity Framework Code First 学习
必备条件
要完成本演练,需要安装 Visual Studio 2010 或 Visual Studio 2012 或者更高。
如果使用的是 Visual Studio 2010,还需要安装 NuGet。
1.创建应用程序
简单起见,我们将构建一个使用 Code First 执行数据访问的基本控制台应用程序。
- 打开 Visual Studio
- “文件”->“新建”->“项目…”
- 从左侧菜单中选择“Windows”和“控制台应用程序”
- 输入 EntityFrameworkStudy 作为名称
- 选择“确定”
可以看到当前的App.config 中的内容如下,等下可以作为参考:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <startup> 4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 5 </startup> 6 </configuration>
2.创建模型
我们使用类来定义一个非常简单的模型。在 Program.cs 文件中进行定义,但是实际应用程序中,可能会将类分为若干个单独的文件,可能作为单独的项目。
在 Program.cs 中的程序类定义下,添加以下两个类。
1 public class Blog 2 { 3 public int BlogId { get; set; } 4 public string Name { get; set; } 5 6 public virtual List<Post> Posts { get; set; } 7 } 8 9 public class Post 10 { 11 public int PostId { get; set; } 12 public string Title { get; set; } 13 public string Content { get; set; } 14 15 public int BlogId { get; set; } 16 public virtual Blog Blog { get; set; } 17 }
可以看到,我们将虚拟化两个导航属性(Blog.Posts 和 Post.Blog)。这将启用实体框架的延迟加载功能。延迟加载意味着,尝试访问这些属性的内容时,将自动从数据库加载(virtual关键字)。
3.创建上下文
现在,可以定义派生上下文,用于表示数据库的一个会话,以便我们查询和保存数据。我们定义一个派生自 System.Data.Entity.DbContext 的上下文,并为模型中的每个类公开一个类型化 DbSet<TEntity>。
现在,开始使用来自实体框架的类型。因此,我们需要添加 EntityFramework NuGet 程序包。
- “项目”–>“管理 NuGet 程序包…”
注意:如果没有“管理 NuGet 程序包…”选项,则应安装 最新版本的 NuGet - 选择“联机”选项卡
- 选择“EntityFramework”程序包
- 单击“安装”
1 <?xml version="1.0" encoding="utf-8"?> 2 <configuration> 3 <configSections> 4 <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> 5 <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> 6 </configSections> 7 <startup> 8 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 9 </startup> 10 <entityFramework> 11 <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> 12 <parameters> 13 <parameter value="v11.0" /> 14 </parameters> 15 </defaultConnectionFactory> 16 <providers> 17 <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> 18 </providers> 19 </entityFramework> 20 </configuration>
App.config中增了的内容已经用黄色标注
增加了文件:
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.1.3" targetFramework="net45" />
</packages>
在 Program.cs 顶部,为 System.Data.Entity 添加一个 using 语句。
using System.Data.Entity;
在 Program.cs 中的 Post 类下,添加以下派生上下文。
1 public class BloggingContext : DbContext 2 { 3 public DbSet<Blog> Blogs { get; set; } 4 public DbSet<Post> Posts { get; set; } 5 }
完整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 方法,如下所示。这些代码为上下文创建一个新实例,然后使用该实例插入新博客。之后,它使用 LINQ 查询检索数据库中的所有博客(按标题的字母顺序进行排序)。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 using (var db = new BloggingContext()) 6 { 7 // Create and save a new Blog 8 Console.Write("Enter a name for a new Blog: "); 9 var name = Console.ReadLine(); 10 11 var blog = new Blog { Name = name }; 12 db.Blogs.Add(blog); 13 db.SaveChanges(); 14 15 // Display all Blogs from the database 16 var query = from b in db.Blogs 17 orderby b.Name 18 select b; 19 20 Console.WriteLine("All blogs in the database:"); 21 foreach (var item in query) 22 { 23 Console.WriteLine(item.Name); 24 } 25 26 Console.WriteLine("Press any key to exit..."); 27 Console.ReadKey(); 28 } 29 } 30 }
我的数据在哪里?
按照约定,DbContext 已经创建了一个数据库。
- 如果本地 SQL Express 实例可用(默认情况下随 Visual Studio 2010 安装),则 Code First 已对该实例创建了数据库
- 如果 SQL Express 不可用,则 Code First 将尝试使用 LocalDb(默认情况下随 Visual Studio 2012 安装)
- 数据库以派生上下文的完全限定名命名,在我们的示例中,名称为 EntityFrameworkStudy.BloggingContext
这些仅仅是默认约定,除此之外,还有多种方式可更改 Code First 所用的数据库。有关更多信息,请参见DbContext 如何发现模型和数据库连接 主题。
可以在 Visual Studio 中使用服务器资源管理器连接至此数据库
- “视图”->“服务器资源管理器”
- 右键单击“数据连接”并选择“添加连接…”
- 如果尚未从服务器资源管理器连接至数据库,则需要选择 Microsoft SQL Server 作为数据源
现在,可以检查 Code First 已经创建的架构。
DbContext 通过查看我们定义的 DbSet 属性,了解模型包含哪些类。随后,它使用 Code First 约定的默认集来确定表和列的名称,确定数据类型,查找主键等。本演练稍后将介绍如何重写这些约定。
5.处理模型更改
现在更改模型,当我们进行更改时,还需要更新数据库架构。为此,我们使用一个称为“Code First 迁移”(或简称“迁移”)的功能。(Migration)
“迁移”是一组有序的步骤,描述如何升级(和降级)数据库架构。这些步骤(称为“迁移”)中的每个步骤均包含一些代码,用于描述要应用的更改。
第一步是为 BloggingContext 启用 Code First 迁移。
- “工具”->“库程序包管理器”->“程序包管理器控制台”
- 在程序包管理器控制台中运行 Enable-Migrations 命令
- 一个新的 Migrations 文件夹已添加至项目中,它包含两个文件:
- Configuration.cs — 此文件包含“迁移”将用来迁移 BloggingContext 的设置。在本演练中不需要进行任何更改,但是,在此处可以指定种子数据、为其他数据库注册提供程序、更改生成迁移的命名空间等。
- <时间戳>_InitialCreate.cs — 这是第一个迁移,它表示已经应用于数据库的更改。应用更改的目的是将其从空数据库迁移至包含博客和文章表的数据库。尽管我们让 Code First 自动创建这些表,现在我们选择“迁移”(已转化为一次“迁移”)。Code First 还在本地数据库中记录:该“迁移”已经应用。文件名中的时间戳用于排序。
现在,更改模型,向 Blog 类添加一个 Url 属性:
1 public class Blog 2 { 3 public int BlogId { get; set; } 4 public string Name { get; set; } 5 public string Url { get; set; } 6 7 public virtual List<Post> Posts { get; set; } 8 }
- 在程序包管理器控制台中运行 Add-Migration AddUrl 命令。
Add-Migration 命令检查自上次迁移后是否有更改,并使用所有更改搭建新迁移。我们可以为迁移指定名称;在本例中,将此迁移称为“AddUrl”。
搭建的代码表明:我们需要向 dbo.Blogs 表添加可容纳字符串数据的 Url 列。如果需要,可以对搭建的代码进行编辑,但是,在本例中,没有这个必要。
1 namespace CodeFirstNewDatabaseSample.Migrations 2 { 3 using System; 4 using System.Data.Entity.Migrations; 5 6 public partial class AddUrl : DbMigration 7 { 8 public override void Up() 9 { 10 AddColumn("dbo.Blogs", "Url", c => c.String()); 11 } 12 13 public override void Down() 14 { 15 DropColumn("dbo.Blogs", "Url"); 16 } 17 } 18 }
- 在程序包管理器控制台中运行 Update-Database 命令。此命令将所有挂起的迁移应用于数据库。InitialCreate 迁移已经应用,因此,这些迁移将仅应用新的 AddUrl 迁移。
提示:在调用 Update-Database 命令查看对数据库执行的 SQL 时,可以使用 –Verbose 开关。
新的 Url 列已添加至数据库中的 Blogs 表:
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 应为用户的主键。
- 我们将使用数据注释,因此需要在 Program.cs 的顶部添加一个 using 语句
using System.ComponentModel.DataAnnotations;
- 现在,注释 Username 属性,将它标识为主键
public class User { [Key]
public string UserId {get;set;} public string Username { get; set; } public string DisplayName { get; set; } }- 使用 Add-Migration AddUser 命令搭建一个迁移,将这些更改应用于数据库
- 运行 Update-Database 命令,将新迁移应用于数据库
EF 支持的完整注释列表为:
- 我们还需要向派生上下文添加一个集
7.Fluent API
上一节介绍了如何使用数据注释来补充或重写按约定检测的内容。另一种模型配置方法是通过 Code First Fluent API。
大多数模型配置都可使用简单数据注释进行。Fluent API 是一种更高级的方法,除某些数据注释不可能支持的更高级配置外,可以指定包含数据注释所有功能的模型配置。数据注释和 Fluent API 可一起使用。
要访问 Fluent API,需要在 DbContext 中重写 OnModelCreating 方法。假设我们需要重命名 User.DisplayName 存储至 display_name 的列。
- 使用以下代码重写 BloggingContext 的 OnModelCreating 方法
1 protected override void OnModelCreating(DbModelBuilder modelBuilder) 2 { 3 modelBuilder.Entity<User>() 4 .Property(u => u.DisplayName) 5 .HasColumnName("display_name"); 6 }
- 使用 Add-Migration ChangeDisplayName 命令搭建迁移,将这些更改应用于数据库。(如果还有其它的更改也想加在这个migration中,可以更改完后再次运行Add-Migration changedisplayname -force命令)
- 运行 Update-Database 命令,将新迁移应用于数据库。
DisplayName 列现在重命名为 display_name: