第二章:Code First概览
如果你使用第一、二版的EF框架工作过,你会回想起书中的业务案例:Break Away Geek Adventures, 简称BAGA。BAGA共享了很多像我们这样的奇客的冒险旅行。但是几年过去了,业务又在增长,到了需要更新应用程序的时候了。既然BAGA为软件奇客服务,他们不能拒绝尝试使用新技术,如EF的Code First.
在这一章里,我们从一个小例子来开始学习Code First的默认行为,然后逐步向这个例子里添加影响这种行为的信息。
我们将开始于BAGA业务域的一个小片断:包括我们旅行的目的地和我们的奇客们在这次旅行的住所。
Code First的美妙在于域类的定义代码与EF数据模型所依赖的代码是一样的。我们只需要开始于代码就可以了,例2-1,分别展示了Destination类和Loadging类。在开始的案例中,我们要保持类的简洁;这些类包含了一些自动属性,并没有什么逻辑。
Example 2-1. The domain model public class Destination { public int DestinationId { get; set; } public string Name { get; set; } public string Country { get; set; } public string Description { get; set; } public byte[] Photo { get; set; } public List<Lodging> Lodgings { get; set; } } public class Lodging { public int LodgingId { get; set; } public string Name { get; set; } public string Owner { get; set; } public bool IsResort { get; set; } public Destination Destination { get; set; } }
Destination类描述了一个特定的BAGA旅行的目的地,对于任何给定的目的地,从Aspen到Zimbabwe,BAGA都会安排各种住所,从床位到五星级酒店,以便到时暂住。因此目的地对象要包含一个或多个居所,使用了List<lodging>来表达;
EF域类介绍
本质上,这些类与EF和Code First无关。这里只简单描述了域的一部分。
为了让EF框架能够找到这些类,需要使用EF框架的context来服务、管理和持久化数据至数据库。EF框架有两种context工具可供选择,一个是ObjectContext,这一工具从EF第一次发布就一直是EF框架的一部分,而随着EF4.1的发布,伴随Code First推出轻量级的DbContext。两种工具都可选用,但更通用(推荐)的是使用新的DbContext,也就是我们马上就要使用的。本书第7章,你将会学习如何使用ObjectContex进行Code First.
我们的BreakAwayContext类,就继承自DbContext,可以获得DbContext的所有功能。除此以外,还需要返回Destination类和Lodging类的可查询数据集DbSets,以暴露类中的属性
Example 2-2 The BreakAwayContext class public class BreakAwayContext : DbContext { public DbSet<Destination> Destinations { get; set; } public DbSet<Lodging> Lodgings { get; set; } }
这个小小的类就代表了你在应用程序中使用的完整的数据层.感谢DbContext,你可查询,修改,跟踪和保存目的地和住所数据.下面创建一个控制台程序使用这个数据层来做一些工作,你会看到我们并非言过其实.
将片断放入程序中
为了查看有关行为,下面将带您实现一个小的VS解决方案,在此你可将这些类放进去,然后创建一个简单的控制台程序来测试你的新数据层.确保你已经开始了正确的路径,本示例将把应用程序的各个层组织在不同的项目中.
1.在VS中创建一个新的解决方案;
2.添加一个类库项目到解决方案,命名为Model.
3.在这个项目里,添加一个新的类命名为Destination.
4.修改Destination类以使其与例2-3一致:
Example 2-3. The Destination class
using System.Collections.Generic;
namespace Model
{
public class Destination
{
public int DestinationId { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public string Description { get; set; }
public byte[] Photo { get; set; }
public List<Lodging> Lodgings { get; set; }
}
}
5.添加另一个类,命名为Lodging,使其与例2-4一致.
Example2-4.The Lodging class
namespace Model
{
public class Lodging
{
public int LodgingId { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public bool IsResort { get; set; }
public Destination Destination { get; set; }
}
}
这就是Model项目的内容.现在,转到数据层,因为域类尚未连接到EF框架上,我们数据层将完全依赖于EF框架.
小贴士:将NuGet安装到VS中就可以将类库方便地添加到项目中.在第1章您已学到EF可以通过NuGet获取.为了完成如下步骤,你需要为VS安装NuGet扩展.为了安装NuGet,选择工具--扩展管理….会出现扩展管理对话框,然后查找NuGet.选择NuGet扩展管理包,点击下载按钮,按照提示安装.
一旦安装完成,您就可以以一种新的方式使用VS了.你可通过VS的工具菜单--添加类库来添加所需的类库,另一种方式是使用上下文菜单来添加,方法是在解决方案的指定项目上点击右键.在下面的操作步骤中,我们使用后者
1.添加另一个类库项目,命名为DataAcess;
2.在项目资源管理器中右键单击新建立的项目选择"Add Livray Package Reference"
3.在弹出的对话框中,选择Online,并搜索Entity Framework;
4.在EF安装包上点击安装按钮,这将把Code First的运行时组件(EntityFramework.dll)添加到你的项目中;
5.右键单击新的项目并选择添加引用;
6.选择项目选项卡将Model proect加入.这将会使context能够访问刚刚在Model Project上创建的域类;
7.添加一个新类,BreakAwayContext到项目中;
8.配置这个新类如代码2-5;
Example 2-5. The BreakAwayContext class
using System.Data.Entity;
using Model;
namespace DataAccess
{
public class BreakAwayContext : DbContext
{
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
}
}
注意类顶部的using语句.一个是System.Data.Entity命名空间.这个命名空间使得你可以访问DbContext和DbSet类;这可能有些令人困惑,命名空间为EntityFramework.dll的程序集是 EntityFramework.dll,而不是System.Data.Entity.dll。System.Data.Entity.dll包含了EF框架的核心,已经是.Net框架的一部分.
现在你的数据访问层已经建好了.可以测试你的数据访问层了.
请等一下,我们还没有告诉数据访问层数据库在哪.还没有连接字符串,没有配置文件,没有关联的数据库.我们要在此利用Code First的第一个功能,数据库初始化.Code First有一系列的步骤来找到数据库并且初始化它.我们现在先使用默认行为,在后面第6章,将会学到更多关于数据库初始化的知识。当然,也可以开始于一个现存的数据库,只是我们现在不这么做。
1.添加一个新的控制台应用程序项目到解决方案,命名为BreakAwayConsole.
2.右键单击新的项目,设置为启动项目;
3.右键单击项目,选择添加类库引用,添加对EF框架的引用;
4.再次右键单击项目,选择添加引用,将Model和DataAcess项目添加至项目;
5.在新项目中,打开Program类;
6.添加两个using语句在文件顶部:
using Model;
using DataAccess;
7.在类中添加一个方法,命名为InsertDestination(代码2-6);
Example 2-6 The InsertDestination Methoed
private static void InsertDestination()
{
var destination = new Destination
{
Country = "Indonesia",
Description = "EcoTourism at its best in exquisite Bali",
Name = "Bali"
};
using (var context = new BreakAwayContext())
{
context.Destinations.Add(destination);
context.SaveChanges();
}
}
8.在类中调用这个方法(代码2-7)
Example 2-7. Calling the InsertDestination method
static void Main()
{
InsertDestination();
}
完成!对这两个域类你只用了少量代码,两行数据访问类以及这个小的应用类,你已经开始准备运行应用程序了。
这个控制台程序并不会显示任何信息.有趣的是发生数据库里的事情:数据库刚才并不存在。
Code First 通过检视Destination 和 Lodging 类确定了数据模型,然后根据类属性推断出数据库的架构模型.既然我们没有提供连接字符串,它就使用默认的约定,查找本地SQL Server Express实例(localhost\SQLEXPERSS)作为数据库宿主,然后以context类的类名DataAcess.BreakAwayContext匹配数据库名.没有找到这样的数据库,Code First就创建它,然后使用它在模型中发现的类依据约定创建表和表的字段。
我们来看看SQL Server Express中Code First创建的数据库(图2-1).
可以看到,DataAccess.BreakAwayContext 有三个表:Destinations ,Lodgings和EdmMetadata,后者用于Code First的数据库初始化,后面我们会提到.
关于表,构架,和字段名的约定
Code First 约定表名使用EF框架的复数化服务(由EF4引入),即使用英语语法的类名复数形式来命名表名。(这个功能可以关闭,见第7章,译者注)默认情况下,每个表都使用dbo构架创建。Code First 属性映射的列使用与类中属性一致的名字命名.
关于主键的约定
再次观察数据库表,你会找到Code First已经使用的默认规则。例如,Code First知道Detination类的DestinationId和Lodging类的LodgingId意思是键值,然后将这些键映射为数据库的主键。注意这些键都是非空主键(PK,notnull)。Code First默认约定将命名为Id或[类名]Id的属性视为类的键。DestinationId 和LodgingId显然符合这一规则。由于这些属性都是整型类型,Code First还会将它们配置为数据库的标识字段。这表示在插入数据时数据库会为这一字段自动生成值。
字符串属性的约定
字符串约定为映射到不限长度的非空列中。由数据库引擎来负责确定映射到何种类型。对于SQL Server而言,默认数据类型为nvarchar(max)。这就是你在图2-1看到的所有的字符串属性都映射成了nvarchar(max)列的缘故---Destination.Name,Country,Description等等.--同时还允许存储空值;
针对Byte数组的约定
Destination类有一个Photo属性,定义为byte[].Code First 约定byte数组映射到不限长度的非空列中.对SQL Server而言就是varbinary(max)类型.
布尔值的约定
Lodging类中的IsResort属性是一个bool类型.由于bool是一个值类型,不能分配给其一个null值.Code First强制要求此列不能为空.对SQL Server而言将bool属性映射为bit 数的库类型.
一对多关系的约定
一个目的地会有很多居所,因此Destination类有一个List<Lodging>属性允许你获取特定目的地的所有居所信息.同时,Lodging类也有一个Destination类型的属性,,因此你可看到某一居所与某一特定的目的地相连接.Code First将这种情况视为一对多关系,约定Lodging表有一个外键约束目的地与居所间的所有关系.
注意尽管在Lodging类中没有外键属性指向Destination类(如DestinationId),Code First仍然使用默认规则创建了一个,形如[Name of navigation property]_[Primary Key of related class](即Destination_DestinationId),这是Code First所为。使得EF框架知道在从数据库中查询或保存时要使用外键.
在目的地和住宿类提供给Code First的导航属性不是一个,而是两个。如果我们只提供了其中之一,关系仍然明显,Code First还是会在数据库中创建外键。还有很多提供外键的约定适用于某些场景,如多对多关系等,在后面我们会深入讨论。
使用配置来覆写约定
正如在第1章学到的,Code First允许你覆写约定,方法是添加配置.可以选择使用Data Annotation的特性标记也可以使用强类型的Fluent API来配置.
您提供的任何配置将会作为模型的一部分,EF框架使用此模型在运行时解析数据。这不仅影响数据库构架,也会影响DbContext内置的验证功能。例如,如果你告诉Code First一个属性是必备的,验证API将让你知道,如果该属性没有被填充的后果。在后续我们将看到这一行为。
Data Annotations是最简单的配置方式,直接应用到你的类和类属性上。这些特性位于System.ComponentModel.DataAnnotations命名空间,目前分布在System.ComponentModel.DataAnnotations.dll和EntityFramework.dll两个程序集中。在未来版本的.NET Framework中,EntityFramework.dll中的Annotations将迁移到System.ComponentModel.DataAnnotations.dll。所以需要在域类的项目中引用上述程序集(取决于您使用的Annotations)。注意Data Annotations能够完成常用的配置,但并非所有Code First配置都可以使用Data Annotation来完成。一些情况下只能使用另一种风格的配置方式:Fluent API。
因为Code First没有自动发现Destination和Lodging类中的某些意图,需要用一些Data Annotations提供额外的配置细节。
小贴士:在C#和Visual Basic应用特性
如果你第一次使用特性,在C#中,特性应使用方括号。例如,使用Annotation 特性Key,在c#中使用[Key],而在Visual Basic中,使用尖括号(<Key>)。当一个特性使用参数时,在C#中是表达了一个等号表达([Table(Schema="baga")]),而Visual Basic使用冒号等号表示(<Table(Schema:="baga")>) 要知道.NET代码中使用特性的更多信息,请参阅MSDN主题"应用特性",在http://msdn.microsoft.com/en-us/library/bfz783fz.aspx。
让我们从Destination类开始.关于这个类我想要作三个调整:
- 必须提供目的地的名字
- 限制描述字段的文本内容在500个字符以内
- 保存照片到SQL Server数据库应为image类型,而不是varbinary(max).
上述需要的Data Annotations特性有两个包含在System.ComponentModel.DataAnnotations.dll程序集中,这是.Net 4的一部分,有一个包含在EntityFramework.dll程序集里,所以需要对两个程序集都要添加引用:
1.在Model项目中,添加对System.ComponentModel.DataAnnotations 程序集的引用.
2.通过类库包装引用添加对EntityFramework程序集的引用.
记住:你必须对每个项目都运行NuGet类库添加向导,即使类库包已经添加到解决方案中的某个项目中.使用同样的步骤将EntityFramework.dll添加到DataAccess项目中.
3.在Destination类顶部,添加System.ComponentModel.DataAnnotations命名空间.
4.修改类如代码2-8所示Example 2-8. Modified Destination class
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Model
{
public class Destination
{
public int DestinationId { get; set; }
[Required]
public string Name { get; set; }
public string Country { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[Column(TypeName = "image")]
public byte[] Photo { get; set; }
public List<Lodging> Lodgings { get; set; }
}
}
Required特性标记不需要附加信息,而MaxLength和Column特性均需要提供参数.这些参数对如何映射到数据库的列进行了明确。我们想要图片储存在SQL Server的image字段里.
小贴士:只要有可能强制将数据库中字段的类型设定为指定类型(例如将byte[]强制指定为image),你可配置数据类型特性。所有的三个特性将会影响数据库的构架,其中之二,Required和MaxLength,也被用于EF框架的验证之用.在观察效果之前,也对Lodging类作些类似的修改.
Annotation特性标记可组合使用,也就是在一个类或属性上可以附加多个annotations特性.我们准备在Lodging.Name属性附加多个配置特性.
添加下述三个注解到Lodging类的Name属性:
[Required]
[MaxLength(200)]
[MinLength(10)]
MinLenght是一个有趣的annotation特性.MaxLength在数据库有对应的含义,而MinLength并不有.MinLength将会用于EF框架的验证,并不会影响数据库.
MinLength只能通过Data Annotations来进行配置,在Fluent API 中无对应项.
理解模型变化如何影响数据库初始化
如果再次运行控制台程序,你会得到一个InvalidOperationException异常.我们刚刚对模型作的修改并没有什么错误,问题在于Code First数据库初始化的默认行为.因此我们需要在继续探索配置时需要修复这些问题.
下面是异常的信息:
The model backing the 'BreakAwayContext' context has changed since the database was created. Either manually delete/update the database, or call Database.SetInitializer with an IDatabaseInitializer instance. For example, the DropCreateDatabaseIfModelChangesstrategy will automatically delete and recreate the database, and optionally seed it with new data.
自上次数据库创建以来,'BreakAwayContext'的上下文已经发生变化.要么手工删除或更新数据库,要么调用IDatabaseInitializer接口的实例 Database.SetInitializer.例如 DropCreateDatabaseIfModelChanges策略将自动删除和再创建数据库,并且可选择使用新数据作为种子.
.默认情况下,Code First只有当数据库不存在的时才创建数据库,但目前数据库已经有了,该怎么办呢?
是否还记得EdmMetadata表?如果没有,回头看一下图2-1.这个表包含了Cord First创建数据模型的部分快照。默认情况下,它会伴随着Code First第一次使用模型建立数据库时创建。然后它就会将表中的版本信息与内存中的模型相比较(这可以从EdmMetadata表中读出)。当Code First识别到上一模型的元数据模型与新建立的模型不匹配,就无法将模型映射到数据库.
您有很多选择.一个是简单删除数据库(包括其中的所有数据),然后让Code First使用默认规则(无数据库--创建新数据库)来使用更新模型创建数据库.这可能很痛苦,特别是你在开发过程中,可能会遇到某些情况导致文件锁定而无法删除.另一种方法,我们马上要用,Code First在遇到模型变化时有一套初始化策略可供使用,该行为删除数据库并重建,默认是封装在名为CreateDatabaseIfNotExists的类中,你可告知正在执行中的程序(本处是指控制台程序)使用哪个策略。
修改Main方法使之与例2-9一样.也需要在添加一个using语句,引用System.Data.Entity.
Example 2-9. Adding Database Initialization to the Main method
static void Main(string[] args)
{
Database.SetInitializer(
new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
InsertDestination();
}
在此代码中我告诉Code First使用初始化器并且指定使用何种策略.(这里使用了DropCreateDatabaseIfModelChanges)应用于上下文(BrakAwayCOntext).现在当你返回应用程序,Code First将识别新旧模型的区别,通知初始化器,删除和重建数据库.
如果你已经在其他地方打开了数据库表读取数据,(例如,在Visual Studio的服务器资源管理器),Code First将不能够删除数据库。在这种情况下,在EF框架尝试删除时会有一个延迟,最终因为无法删除而抛出一个异常。似乎这样做太繁琐,特别是当我在用户组和会议展示时。我遇到的一个常见的场景是,我已打开了SQL Server Management Studio(SSMS)中,对数据库进行一些查询。你不得不关闭SSMS的数据库完全释放对数据库的锁定。
Destination类之Destinations表有三个可见的改变,Name设定为必须,在数据库表现为非空字段.Description现在是nvarchar(500)而不再是max,Photo也已经是一个image数据类型.Lodging表也得到影响,Name现在限制为200个字符且为非空型,第三个annotation:MinLength在数据库构架中没有等效值,因此在此忽略.但是,EF框架将会对此进行验证.
Data Annotations和前端验证提示
NET 4中引入的Data Annotations,可以用于ASP.NET(含.Net MVC)作为用户界面的动态数据验证工具来使用。许多Code First 使用的annotations特性标记都来自源于System.ComponentModel.DataAnnotations程序集。因此,这些数据验证功能是.Net程序集的的内置特性。
例如,"Required"特性在.NET4的程序集中,而不在EntityFramework.dll程序集里。因此,如果你在MVC应用程序中使用的Code First类,并且忽略填写一个必须输入的属性,MVC用户界面验证会响应,你可以在图2-3看到。
DbContext还提供了一个验证API,具有服务器端验证的能力,这与使用Data Annotations特性标识还是使用Fluent API来配置类是没有关系的。但是,使用Data Annotations时,MVC与特性更好地配合,实现一些验证API的功能。验证API的细节内容不包括在这本书中,本书的重点在于Code First建模,你可在另一本书:Entity Framework:DbContext找到与验证API有关的信息。
使用Fluent API 配置Code First
使用Data Annotations配置很简单,也可能是你想要的。但是Data Annotations只能获得一部分配置的功能。这时,Fluent API可以提供更多的功能,基于这个原因你可能会愿意用它.
小贴士:Fluent API是什么?
Fluent API并非专用于Code First或EF框架。Fluent API的基本思路是使用链方法调用程序代码,这样代码很容易被开发者所阅读。每一个调用的返回类型都定义为下个调用的有效方法.例如,在Code First的Fluent API中,你可以使用Entity方法选择一个实体来配置.智能感知将会显示出所可以用来配置的方法.如果你使用属性方法选择属性来配置,你就看到所有为特定属性所配置的方法.
还有另一个原因解释为什么开发者首选Fluent API。在将标记应用到你的美妙域类上时,需要定义越来越多的烦人的标记。目前还只是一些验证逻辑,当你学到更多Data Annotations配置特性的时候,你就会发现需要更多的信息指导类如何映射到数据库。如果你喜欢清洁的类,不想让类包含那么多的标记,就可以选择Fluent API。Code First的一个优势就是能够使用自己的类构建EF框架,一个充斥着数据库表名或列类型的类显然不够简洁,Fluent API解决了这一问题,我们来看这是如何实现的。
小贴士:跟随Data Annotations还是跟随Fluent API ?
在本书后续内容中,你将会看到很多例子来说明如何配置映射,有Data Annotations的,也有使用Fluent API的.
许多映射只能通过Fluent API获得.如果你在VS中跟随我们学习,强烈推荐您创建两个单独的解决方案。否则,当使用了Data Annotation中还想看看在Fluent API中同样的配置效果,你就不得不注释掉有关代码。然后当你使用另一个Data Annotation特性标记,你又不得不注释掉Fluent API代码.在注释与取消注释之间来回调整,万一出现错误就会导致抛出异常,要么是配置重复要么是配置丢失,可能会非常麻烦。
这就意味着在创建新类或添加新方法时需要进行许多必要的复制/粘贴操作.也意味着Data Annotations 只能应用于一个解决方案而Fluent 配置被强制用于另一个.
如果你遵循此建议,还有一个附加的建议.确保在两个解决方案使用不同的命名空间。如果其中一个解决方案使用了DataLayerForAnnotations命名空间,其数据库将为DataLayerForAnnotations.BrakeAwayContext。另一解决方案使用了DataLayerForFluent,命名空间,其数据库将为DataLayerForFluent.BrakeAwayContext。你会看到有两个独立的数据库,很容易理解哪个解决方案影响了哪个数据库。
DbContext首先在类中查找可以获取的信息。这时,context已经准备好解析模型,但对开发者来说有一个机会来中断context和执行附加配置的连接.这得益于DbContext.OnModelCreating方法,这是在模型创建前被上下文所调用的方法。方法是虚拟的,因此你可以覆写并加入你自已的逻辑代码.这就Fluent API进行配置的入口.
此方法的声明方式如下:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
提供给OnModelCreating的参数DbModelBuilder是你需要添加配置的类。DbModelBuilder使用泛型和lambda表达式,所以编码是强类型的可以协助您进行配置的设置。首先要告诉DbModerBuilder对哪个类(实体)实施配置:
modelBuilder.Entity<Destination>() ;
你可以配置类映射到数据库的表名:
modelBuilder.Entity<Destination>().ToTable("a_table_name");
你也可配置类的属性.如果想要配置属性,应进一步演进:
modelBuilder.Entity<Destination>()
.Property(d => d.Description).HasMaxLength(500);
代码2-10重写了前面使用data annotations建立的配置。OnModelCrating是DbContext的一个方法,请确保它在BrakAwayContext类里。
Example 2-10. Configuring with the Fluent API
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Destination>()
.Property(d => d.Name).IsRequired();
modelBuilder.Entity<Destination>()
.Property(d => d.Description).HasMaxLength(500);
modelBuilder.Entity<Destination>()
.Property(d => d.Photo).HasColumnType("image");
modelBuilder.Entity<Lodging>()
.Property(l => l.Name).IsRequired().HasMaxLength(200);
}
如果你疑惑HasMinLength哪里去了,在Fluent配置方法里没有最小长度的配置,因为这不会对数据库的行产生影响.
除非要配合使用Data Annotations 和 Fluent API为了保持代码一致最好选择其一。为了让代码更流畅,我们已经移除了所有的Data Annotation标记.实际上Model项目也不需要对System.Component Model.DataAnnotations 或者EntityFramework程序集的引用.
再次运行代码,Code First将对比模型和数据库中的EdmMeta表,尽管我们修改了配置代码,最终结果还是一样的.同样的destination再次加入数据库中结束与匹配的记录.在图2-4你可以看到复制到的数据.
组织Fluent配置
如果你有很多配置需要执行,OnModelCreating 可能很快不堪重负(代码太多)。应该使用位于EntityTypeConfiguration的实体类来分组配置,然后在OnModelCretaing方法中告诉DbModelBuilde有些实体类r。DbModelBulider有一个Configruation属性可以来增加EntityTypeConfigurations(实体类型配置).
例2-111展示了对Destinaton类和Lodging类的分组情况:
Example 2-11. Organizing configs into separate EntityTypeConfiguration classes
using System.Data.Entity.ModelConfiguration;
using Model;
public class DestinationConfiguration :
EntityTypeConfiguration<Destination>
{
public DestinationConfiguration()
{
Property(d => d.Name).IsRequired();
Property(d =>
.Description).HasMaxLength(500);
Property(d => d.Photo).HasColumnType("image");
}
}
public class LodgingConfiguration :
EntityTypeConfiguration<Lodging>
{
public LodgingConfiguration()
{
Property(l => l.Name).IsRequired().HasMaxLength(200);
}
}
这些代码在OnModelCreating方法内部的时候,由DbModelBuilder开始,后面跟着Entity方法来确定哪个实体进行配置。在EntityConfiguration类中,是由继承于EntityTypeConfiguration类的类开始的,这里的实体类型已经指定。例如,modelBuilder.Entity<Destination>().Property这个语句是对属性进行设置,而调用的modelBuilder.Entity<Destination>()实际上是创建了一个EntityTypeConfiguration<Destination>对象并返回给你,然后在属性上配置。所以无论使用哪种方式,访问的是同一个API。
代码2-12,你会看到修改的OnModelCrating方法,来使用这些类.
Example 2-12. Adding the configuration classes in OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new DestinationConfiguration());
modelBuilder.Configurations.Add(new LodgingConfiguration());
}
随着我们转向Fluent配置的例子,我们有时会将配置放在EntityTypeConfiguration类,有时放置在modelBulider中。这仅仅是为了让你继续看到这两种语法。但是,在最终产品中,很显然不能混用配置。你更希望有一个统一的方式,无论是将所有配置都写在OnModelCrating方法内部还是组织在EntityTypeConfiguration类中。你会看到有几个配置操作不是类型相关的,必须要直接在OnModelCreating方法中调用。
小结
本章对Code First作了基础介绍.我们看到Code First的约定规则非常智能,能够通过类的构建获知您的意图。当约定无法正确推断时,你需要使用配置明确控制Code First如何构建模型和数据库构架。我们介绍可以直接在类上采用Data Annotations的特性来实现配置。而对那些更愿意让域类独立的情形,你还学到可以使用替代的Fluent API在DbContext类中来实施配置.
Code First 可以自动为你构建数据库。你已经看到如何施加控制到其响应以对模型作出调整。期待重建数据库来匹配新的模型.现在你应该已经喜欢上了Code First,我们将进一步深入。扩展模型,学习使用Code First的来龙去脉.