在开发面向数据的软件时我们常常为了解决业务问题实体、关系和逻辑构建模型而费尽心机,ORM的产生为我们提供了一种优雅的解决方案。ADO.NET Entity Framework是.NET开发中一种由ADO.NET驱动的ORM框架,使用Entity Framework开发人员可以不必考虑数据的基础数据表和列,在处理数据时能够以更高的抽象级别进行工作,并能够以相对传统开发编写更少的代码来创建和维护应用程序。
我们知道面向对象的编程与数据存储系统的交换提出了一个难题:类结构通常同关系数据表组织结构相近但又不同。例如数据中可能使用一个外键表示一个实体与另一个实体的关系,但是对于类而言我们通知会在类中定义一个属性来描述这种关系。对于这个问题当前ORM框架一般选择通过将面向对象的类和属性映射到关系表和列来弥补这种不足。但是Entity Framework并没有采取这种方式而是将逻辑模型中的表、列和外键约束映射到概念模型中的实体和关系,实体数据模型工具再基于概念模型生成可扩展的数据类。这些类派生自基类,而基类提供服务以将实体具体化为对象并进行跟踪和保存。这样一来开发人员不仅可以很方便的对数据类进行扩展而且可以像处理关联对象一样处理实体和关系。
三种编程方式
Entity Framework4.1之前EF支持“Database First”和“Model First”编程方式,从EF4.1开始EF开始支持支持“Code First”编程方式,今天简单看一下EF三种编程方式。
开始介绍这三种EF操作方式之前,首先在Visual Studio 2013中建立一个数据库连接,这里我们以“AdventureWorks”数据库为例:
Database First
“Database First”模式我们称之为“数据库优先”,前提是你的应用已经有相应的数据库,你可以使用EF设计工具根据数据库生成数据数据类,你可以使用Visual Studio模型设计器修改这些模型之间对应关系。
首先创建一个控制台应用程序,然后右键添加新建项,选择“ADO.NET Entity Data Model”,名称输入AdventureWorksContext:
接着选择从数据库生成:
下一步,选择数据库连接“SQL2008.AdventureWorks.dbo”:
点击下一步,然后选择“Person”和“PersonPhone”表:
创建完模型之后,你会发现Visual Studio自动为你生成了“Perrson”、“PersonPhone”两个实体类和一个“AdventureWorksContext”数据库上下文操作类:
下面简单的看一下如何使用EF进行数据查询,通过下面的代码我们可以看到EF对于数据的操作入多么优雅:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DatabaseFirst { class Program { static void Main(string[] args) { using (var db = new AdventureWorksEntities()) { IQueryable<Person> persons = from people in db.People where people.LastName == "Sánchez" select people; foreach (Person p in persons) { Console.WriteLine("first name is :{0}", p.FirstName); if (p.FirstName == "Angela") { p.AdditionalContactInfo = "other info"; } } var persons2 = from people in db.People where people.LastName == "Sánchez" && people.FirstName == "Angela" select people; if (persons2.Count() > 0) { Console.WriteLine("additional contact information is :{0}", persons2.First().AdditionalContactInfo); } //上面虽然可以查出来AddtionalContactInfo,但是实际省并未保存到数据库,具体保存方法在此不再详细描述 } } } }
执行结果如下图:
注意:如果你的数据库表结构发生改变后,只需在模型设计视图空白处右键,选择“从数据库更新模型”接着按照向导操作即可。 |
Model First
Model First我们称之为“模型优先”,这里的模型指的是“ADO.NET Entity Framework Data Model”,此时你的应用并没有设计相关数据库,在Visual Studio中我们通过设计对于的数据模型来生成数据库和数据类。
首先创建一个控制台应用程序,右键添加新建项,选择“ADO.NET Entity Data Model”,名称输入AdventureWorksContext:
接着选择空模型:
在模型设计视图中,添加新实体:
输入实体对应的属性:
添加,两个Scalar属性:“Customer”和“OrderDate”;同样的方式添加第二个实体“OrderDetail”,并添加“Product”和“UnitPrice”属性:
接下来我们添加二者之间的关系,“Order”和“OrderDetail”是一对多的关系,“Order”可以通过“OrderDetails”属性访问“OrderDetail”实体,“OrderDetail”可以通过“Order”属性访问“Order”实体,并且添加了一个外键约束到“OrderDetail”中:
添加过关系后:
到目前为止Model First中的Model已经创建结束,下面就需要生成到数据库了,在模型设计视图空白处选择“从模型生成到数据库…”:
选择数据库连接,点击下一步,你将会看到生成的sql语句:
点击完成,不出意外的话将打开生成的脚本,当然你也可能会出现如下错误,请下载最新的SQL Server Data Tool(我本地VS2012,数据库SQLServer2008R2出现了下面的提示,下载更新即可,建议直接下载镜像文件):
此时生成了数据库上下文和实体类,并且打开了建表的脚本:
打开的数据库脚本:
然后右键选择执行即可。
然后编码查询一下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ModelFirst { class Program { static void Main(string[] args) { using(var dbContext=new AdventureWorksContextContainer()) { var o = new Order(); o.OrderDate = DateTime.Now; ctx.Orders.Add(o); ctx.SaveChanges(); var orders = from od in dbContext.Orders select od; foreach (Order order2 in orders) { Console.WriteLine("OrderID:{0},OrderDate:{1}",order2.Id,order2.OrderDate); } Console.Read(); } } } }
查询结果:
注意:如果我们的模型发生改变,只需要在模型设计视图修改模型,让后保存此时实体类就会相应改变,然后选择“从模型生成到数据库”重新执行生成的脚本即可。 |
Code First
Code First模式我们称之为“代码优先”模式,是从EF4.1开始新建加入的功能。使用Code First模式进行EF开发时开发人员只需要编写对应的数据类(其实就是领域模型的实现过程),然后自动生成数据库。这样设计的好处在于我们可以针对概念模型进行所有数据操作而不必关系数据的存储关系,使我们可以更加自然的采用面向对象的方式进行面向数据的应用程序开发。
从某种角度来看,其实“Code First”和“Model First”区别并不是太明显,只是它不借助于实体数据模型设计器,而是直接通过编码(数据类)方式设计实体模型(这也是为什么最开始“Code First”被叫做“Code Only”的原因)。但是对于EF它的处理过程有所差别,例如我们使用Code First就不再需要EDM文件,所有的映射通过“数据注释”和“fluent API”进行映射和配置。另外需要注意的是“Code First”并不代表一定就必须通过数据类来定义模型,事实上也可以通过现有数据库生成数据类。
那么我们首先看一下传统的Code First如何使用。
首先创建一个控制台应用程序,接下来添加两个类“Order”和“OrderDetail”,我们可以看到这两个类只是简单的C#对象(POCO,Plain Old C# Object)这两个类基本和EF没有任何关系,需要注意的是这两个类有两个导航属性“Order.OrderDetails”和“OrderDetail.Order”:
using System; using System.Collections.Generic; namespace CodeFirst { public class Order { public int Id { get; set; } public string Customer { get; set; } public System.DateTime OrderDate { get; set; } public virtual List<OrderDetail> OrderDetails { get; set; } } }
using System; using System.Collections.Generic; namespace CodeFirst { public partial class OrderDetail { public int Id { get; set; } public string Product { get; set; } public string UnitPrice { get; set; } public int OrderId { get; set; } public virtual Order Order { get; set; } } }
有了这两个类之后让我们定义一个数据库上下文,有了它我们就可以对数据进行增删改查操作了,这个类必须继承于"System.Data.Entity.DbContext”类以赋予它数据操作能力。因此接下来我们需要给这个应用安装EntityFramework包,因为到目前为止我们并没有引入EF框架相关的任何内容,我们需要引入EF相关程序集。但是我们有更好的选择那就是NuGet。通过NuGet进行在线安装:项目中右键选择"Manage NuGet Packages…”;选择Online;再选择“EntityFramework”;然后点击安装即可。不了解NuGet的朋友到这里看一下使用 NuGet 管理项目库。数据库上下文操作类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; namespace CodeFirst { public class OrderContext:DbContext { public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } } }
然后我们进行测试,在这个类中我们首先创建了一个Order实例,接着编码查询:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CodeFirst { class Program { static void Main(string[] args) { using (var ctx = new OrderContext()) { var o = new Order(); o.OrderDate = DateTime.Now; ctx.Orders.Add(o); ctx.SaveChanges(); var query = from order in ctx.Orders select order; foreach (var q in query) { Console.WriteLine("OrderId:{0},OrderDate:{1}", q.Id, q.OrderDate); } Console.Read(); } } } }
查询结果如图:
如果是第一次使用EF Code First的朋友一定会有疑问,我们没有进行任何数据库配置,增加了一条数据通过查询确实保存上了,那么我们的数据到底在哪呢?事实上如果用户不进行数据库配置EF默认会使用“.SQLEXPRESS”数据库实例,如果你没有安装“.SQLEXPRESS”则默认使用LocalDb,关于LocalDb的具体细节请看:SQL Server 2012 Express LocalDB,例如我本机就存放在:“C:UsersKenshin”,可以看到创建了一个名为“CodeFirst.OrderContext”的数据库:
但是多数情况下我们是希望自己控制这个数据库的,例如我想让他保存在我机器上的”.SQL2008”实例上,此时我们就需要在配置文件App.Config中配置一个数据库连接串,然后在我们的数据库上下文中指定这个连接名称。
配置文件:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <connectionStrings> <add name="CodeFirstDb" connectionString="Data Source=.SQL2008;Database=CodeFirstDb;UID=sa;PWD=123;" providerName="System.Data.SqlClient"></add> </connectionStrings> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> </entityFramework> </configuration>
OrderContext类,构造函数多了一个连接名参数:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; namespace CodeFirst { public class OrderContext:DbContext { public OrderContext(string connectionName) : base(connectionName) { } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } } }
使用的时候,传入配置的数据库连接字符串名称:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CodeFirst { class Program { static void Main(string[] args) { using (var ctx = new OrderContext("CodeFirstDb")) { var o = new Order(); o.OrderDate = DateTime.Now; ctx.Orders.Add(o); ctx.SaveChanges(); var query = from order in ctx.Orders select order; foreach (var q in query) { Console.WriteLine("OrderId:{0},OrderDate:{1}", q.Id, q.OrderDate); } Console.Read(); } } } }
执行之后就会发现在”.SQL2008”实例上多了一个“CodeFirstDb”数据库(注意图中除了我们创建的两个实体表还有一个系统表dbo._MigrationHistory它记录了模型的定义,在以后的文章中我会着重解释此表):
到了这里我们三种EF编程方式已经全部介绍完了,关于Code First模式如何更新模型、如何先建数据库再生成数据类以及更多EF问题在接下来的文章中再探讨。