使用EF“代码优先”函数库操作现有数据库

【原文地址】Using EF “Code First” with an Existing Database

【原文发表日期】 2010/8/4 11:09 AM

 

上个月我博客里提到了Entity Framework 4新的“代码优先”开发模式。EF“代码优先”类库启用超棒的编码至上的开发工作流来访问数据。它让你可以:

· 不需要打开一个设计工具或定义XML映射文件就可以进行开发。

· 定义模型对象时只需写一个简单的类,而不需要指定基类。

· 使用“惯例高于配置”的方式实现数据库持久化,不需要显式配置任何信息。

在我的第一篇文章里我介绍了EF“代码优先”类库,并示范了使用EF4默认的映射惯例来创建一个新的数据库。这些默认惯例在新的应用程序里工作的很好,而且还避免你显式配置任何东西从/往数据库中映射类型。后来我又写了第二篇自定义数据库结构映射的文章,讨论了覆盖默认的持久化映射规则,和自定义数据库结构的方法。

今天的文章我将回答最近很多人问我的问题:“如何对现有的数据库使用EF代码优先类库?”

使用EF“代码优先”类库操作现有数据库

实际上,EF代码优先类库和现有的数据库工作的非常好,而且还是一个很好的编码至上的开发模式。特别是,你可以用“简单CLR对象”(即POCO)定义好模型对象,用默认的映射惯例,或用自定义的映射规则,从/往数据库里映射它们。

下面分步示范了使用EF“代码优先”类库操作现有数据库的方法。

第一步:创建一个新的ASP.NET Web应用程序项目

让我们从创建一个新的ASP.NET Web应用程序项目开始。我前两篇EF“代码优先”文章用的是ASP.NET MVC—这篇文章我将用ASP.NET Web窗体(来实现)。请注意,不论你使用哪种类型的ASP.NET应用程序,背后的EF理念都是相同的。

我们使用VS 2010(或者免费的Visual Web Developer 2010体验版)的“文件->新建项目”菜单,并选择“ASP.NET Web应用程序”项目模板来创建新的程序。

VS 2010里新的“ASP.NET Web应用程序”项目是一个很好的初学者模板,它的默认母板布局页面设计使用的是CSS(我在以前的文章里提到了新的初学者项目模版)。当创建完毕以后,你可以看到它里面包含下面这些文件:

我们并不需要这些文件(实际上也可以使用“ASP.NET空Web应用程序”模版)—但如果用它们的话,可以让我们的程序看起来更漂亮一些。

第二步:添加对EF代码优先库的引用

下一步将为我们的项目添加对EF代码优先类库的引用。在解决方案资源管理器上右击“引用”节点,并选择“添加引用”。

你需要引用“Microsoft.Data.Entity.Ctp.dll”文件,你下载并安装EF代码优先类库后,它安装在“\Program Files\Microsoft ADO.NET Entity Framework Feature CTP4\Binaries\”文件夹里。添加完引用后,项目引用窗口类似下图:

第三步:Northwind数据库

如果你的SQL Server数据库已经安装了Northwind(或者其它的数据库),你可以跳过这一步。

如果你尚未安装,可以在这里下载它。你既可以使用里面的.SQL文件来将它安装到SQL数据库里,也可以拷贝Northwind.mdf这个SQL Express文件到你应用程序的\App_Data文件夹里:

第四步:创建我们的模型层

现在我们来写模型类,并使用EF“代码优先”类库将它们映射到我们的Northwind数据库。下面就是所有要写的代码—就这些:

下面是这些代码的详细工作原理:

POCO模型类

EF“代码优先”类库允许我们使用“简单CLR对象”(即POCO)来表现数据库里的实体。也就是说我们的模型类不需要继承自一个基类,也不需要实现任何的接口或特性(attribute)。这样可以使我们的模型类型保持整洁并且“持久化无关”。

前面我们定义了两个POCO类—“Product” (产品)和“Category” (种类)—用来表示Northwind数据库里的“Product” (产品)和“Category” (种类)表。两个类的属性分别对应表的各个列。“Product” (产品)或“Category” (种类)的实例分别对应表里的一行。

可空列

请注意“Product” (产品)类里有些属性被定义成可空属性(即Decimal?—说明它是一个可空类型)。数据表的可空列(Nullable column)的类型如果是值类型的话,在模型类里应该用可空属性表示:

如果在模型类中,你不会访问可空列,你可以选择省略它。比如说,Northwind的“Product” (产品)表里的“QuantityPerUnit”是一个可空的nvarchar类型,而“UnitsOnOrder”列是一个可空的smallint型。我在定义上面的“Product” (产品)类时省略掉了这些属性。因为它们在数据库里是可空的,所以我在执行读取、插入、更新和删除操作时不会有任何问题。

关联属性和延迟加载

EF“代码优先”类库使应用数据库里的主键/外键关系时变得更容易,它们通过模型类的属性表示,我们可以通过遍历模型类来使用这些关系。

前面我们在“Product” (产品)类里定义了一个“Category” (种类)属性,在“Category” (种类)类里定义了一个“Product” (产品)属性。通过访问这些属性,可以让我们使用两个表之间的主键/外键关系,并获取相关模型实例的引用。注意,这些属性依然是“POCO”对象,不需要使用任何EF定义的集合类型来定义它们。

标记为“virtual”的关联属性默认采用的是延迟加载技术。也就是说,如果你抓取的是“Product” (产品)实体,除非显式访问,则不会从数据库中加载它的“Category” (种类)方面的信息(除非当你在使用LINQ查询抓取Product对象时,显式指定获取Category的数据) 。

EF场景(Context)类

一旦创建好了“Product” (产品)和“Category” (种类)POCO类,我们用EF“代码优先”类库来创建一个”场景(context)”类来从/往数据库映射我们的POCO模型类:

上面的“Northwind”类是一个场景(Context)类,用来从/往数据库映射我们的POCO模型类。它从EF“代码优先”类库里的DbContext这个基类继承下来,通过两个属性来陈列数据库中相应的表。在这个示例中,我们用默认的“惯例高于配置”映射规则来从/往数据库映射类型。

如果我们要让模型类的对象模型跟数据库的结构有些差异,我们也可以覆写“OnModelCreating”函数来指定自定义的映射规则。我前面的EF“代码优先”类库文章讲解了相关方法。

第五步:配置数据库连接字符串

我们已经写好了定义模型层的所有代码,在使用它之前最后一步是设置连接到数据库的连接字符串。

在我的第一篇EF“代码优先”类库文章里,我介绍了EF“代码优先”类库提供的一个很酷的选项,就是它可以为你自动创建/重新创建数据库的结构。这个选项对新建应用系统开发的情况特别有用—它允许你在项目早期只关注与模型层,而不需要在模型改动时,花时间更新你的数据库结构。

更重要的是,这个数据库自动创建功能只是一个选项—它不是必须的。如果你的数据库连接字符串指向的是一个已有的数据库,那EF“代码优先”类库不会自动创建一个新的。而且这个自动创建选项只在你显式指明的情况下EF才会这样做—因此你不必担心它会删除并重建你的数据库,除非你显式地告诉它应该这么做。

在这篇文章里,我们不会自动创建数据库。而是通过在web.config文件里添加一个“Northwind”连接字符串指向我们已有的Northwind数据库:

  <connectionStrings>

FakePre-5c9bbb43f6b54062965459a8019f6a55-7c582b615aa14a8e92d75f8aae078dd6FakePre-e59620839b4a4fd28482c32c91af2d43-2644b06d2fc3483db47da600a3fed655FakePre-40e9af4a29194d639b2f601b11527953-9d7dbc80fa7944f18b55b8b6b11d0239FakePre-37c9aea4db474f889111625823adfe59-d8d71c1777fd49cda293c2a8468c0563

EF“代码优先”类库在查找连接字符串时,按照惯例,根据场景(Context)类名来寻找。因为我们的场景(Context)类名字叫“Northwind”,所以默认寻找名为“Northwind”连接字符串。上面我们配置Northwind连接字符串使用本地的SQL Express数据库,你也可以指向一个远程的SQL服务器。

第六步:使用我们的模型类

现在让我们使用Northwind模型类写一个非常简单的页面,从数据库中显示一些数据。

我们从向ASP.NET项目,右击web项目,然后选择“添加->新项”,然后选择“使用母板的Web窗体(Web Form using Master Page)”文件模板。我们把这个页面命名为“Product.aspx”并且采用ASP.NET Web项目默认的初学者模板自带的“Site.master”母板页。

我们将在Product.aspx页面添加一个<asp:GridView>控件。并配置它只显示Products(产品)的名称和价格:

而在后台文件里,通过编写下列的LINQ查询从我们的模型类来获取数据库中所有有效的产品(Product),并将它们绑定到上面的GridView控件上:

现在当我们运行项目并浏览Products.aspx页面时,我们可以得到类似下图的产品列表:

现在我们已经有了一个简单的应用程序,它基于现有数据库调用EF“代码优先”类库。

下载示例代码

你可以从这里下载完整的示例代码,它假设你已经安装了EF“代码优先”类库 CTP 4版本和SQL Express。

其它代码示例

下面的代码演示了使用Northwind模型的其它常见用法。

跨关系查询

下面的LINQ查询演示了基于产品种类(Category)的名称来获取产品(Product)对象序列。注意下图我们的LINQ查询跨越产品(Product)对象和与其相关的种类(Category)对象的属性的编写方式。实际的过滤过程是在数据库引擎里面做的—中间层只需要返回产品(Product)对象集合(也就更有效率):

使用Find方法获取单个产品(Product)对象

除了允许你编写上面的LINQ查询以外,EF“代码优先”类库还支持对DbSet<T>集合调用“Find()”方法,这样你就可以用类似下面的代码根据ID获取单个实例了:

插入一个新的种类(Category)

下面的代码演示了往数据库中添加一个新的种类(Category)的方法:

留意我们创建种类(Category)对象,给它的属性赋值,然后将它加到语境(Context)的Categories集合的方式。接着我们调用语境(Context)对象的SaveChanges()把改动保存到数据库中。

添加一个新的种类(Category)和产品(Product)(并关联它们)

下面的代码演示了创建一个新的种类(Category)和一个新的产品(Product),并关联产品(Product)属于新建的种类(Category),最后将它们都保存进数据库:

留意上面我们可以通过将新建种类(Category)的实例赋值给新建产品(Product)的“Category” (种类)属性, 将新建产品(Product)引用到新建种类(Category)实例。我们不需要显式地设置CategoryID外键属性—这个会在保存进数据库时自动完成。

EF使用一个叫做“工作单元”的模式—它会跟踪语境(Context)里的多重改动,当“SaveChanges()”被调用时,它会将这些改动以原子事务的方式一并保存(要么全部成功保存,要么全部失败)。这是保证你的数据库不会被污染(即有些保存了,有些却没有)的简便方法。

在上面的代码中,种类(Category)和产品(Product)要么全保存了,要么全部没有(会扔出来有一个异常)。

更新一个产品(Product)并保存

下面的代码演示了获取和更新一个产品(Product),并保存回数据库的方式。前面我示例了使用Find()函数根据ProductID获取单个产品(Product)的方法。下面我们写一个根据ProductName获取特定产品(Product)的LINQ查询:

我们可以做多次更改(任何对象,新建的对象也是)。当我们调用SaveChanges()时,会以一个事务的形式将它们保存到数据库中。

默认惯例 vs 自定义映射规则

之前我们创建产品(Product)和种类(Category)类时,我们使用的是EF“代码优先”类库默认的惯例从/往数据库中映射类型。这让我们避免了自己指定映射规则,保持干净的代码。

当然肯定也有你不喜欢数据库结构的时候,而希望有自己不同形状的模型类。参见我的自定义数据库结构映射文章来了解使用EF指定自定义映射规则的方法。这些方法也同样适用于已有的数据库。

总结

我的确很喜欢EF“代码优先”类库的功能,并认为它提供了极佳的编码至上的数据处理方式。它很好,很强大。我喜欢它是因为它让代码保持干净,可维护并且很简洁。希望这三篇文章能够让你一瞥它的功能 — 针对新的或已有的数据库(的功能)。

你可以从这里下载EF“代码优先”类库 CTP 4版本。未尽事宜请参看ADO.NET团队的文章:

· EF CTP 4发布公报

· EF CTP 4工作效率上的提高

· EF CTP 4代码优先类库攻略

· 数据注解(DataAnnotation)和代码优先类库

· 代码优先类库的默认公约

· Scott Hanselman的CTP 4攻略

希望这能对您有所帮助。

Scott


上一篇博客:Visual Studio 2010 Power Tool扩展所提供的一些很酷的代码编辑功能