.NET Framework自2002年发布以来,已经历了十来个年头。相应的,.NET平台上的数据访问技术也在不断发展,从最基础的ADO.NET,到SqlHelper简单帮助类,到DAAB(Data Access Application Block),再到LINQ,最终演变为现在微软主推的Entity Framework,一路走来可算不易。同时,一些优秀的开源项目也不断涌现,如从Java平台移植过来的NHibernate;源自.NET平台的Castle ActiveRecord;博客园网友原创的NBear;还有我曾经在一个项目中用过的非主流技术Grovekit等等。下图对.NET平台的一些主流数据访问技术进行了一个整体展示:
图1 .NET平台数据访问技术汇总图
本文接下来将对ADO.NET、SqlHelper、DAAB、NHibernate、LINQ和Entity Framework几种技术在技术架构和使用方法上作简要介绍,并使用这些技术实现对同一个数据对象的增删改查功能,最后对这些技术进行对比分析。希望本文对你了解.NET平台的数据访问技术有所帮助。
ADO.NET是.NET平台数据访问的基础,接下来要讨论的每一种技术都是建立在他的基础上的。虽然我不提倡凡是都直接用ADO.NET,因为那样会让你的数据层代码量激增;但是作为.NET程序员,理解ADO.NET技术的主要内容是绝对必要的。
ADO.NET是由ASP时代的ADO(ActiveX Data Objects)演变而来,用于应用程序与数据源进行交互。ADO.NET的技术架构如下图所示:
图2 ADO.NET技术架构图
如上图所示,ADO.NET技术由两部分组成,一部分是.NET Framework数据提供程序,它主要包含Connection对象(用于与数据源进行连接)、Command对象(用于执行数据CRUD命令)、DataReader(以只进的方式一次读取一条数据库记录)对象和DataAdapter对象(通过SelectCommand、InsertCommand、UpdateCommand和DeleteCommand命令实现数据源与DataSet的适配)的实现。.NET Framework默认实现了SQL Server(System.Data.SqlClient)、Oracle(System.Data.OracleClient)、OLEDB(System.Data.OleDb)和ODBC(System.Data.Odbc)数据源提供程序。也就是说,对上述这几种数据源可以直接进行访问,不需要实现数据提供程序接口;而访问其他的数据源,如MySQL、DB2、其他国产数据库等,均需实现数据提供程序接口,当然,这项工作一般是由数据库厂商来完成,作为程序员,我们只需要通过某种途径获得该实现库并引用就行了。
另一部分是DataSet对象,它独立于任何数据源,即可以通过DataAdapter对象将数据库中的数据适配成DataSet对象,也可以将一个XML数据文件序列化为DataSet对象。DataSet对象与前面提到的DataReader对象有很大的不同,DataReader对象是只进且只读的,并且不驻留内存,因此读取数据的过程中需要始终保持数据库连接;DataSet对象以DataTable对象集合的形式驻留内存,因此数据一旦加载,便可断开数据源连接,这一点很关键,因为数据连接在程序的运行时往往是很宝贵的。
ADO.NET的使用大致可以分为配置数据库连接、连接数据库、执行数据操作命令和关闭连接这几个步骤:
一、配置数据库连接
无论是B/S应用程序配置文件Web.config,还是其他应用程序(包括)配置文件App.config,均提供了数据源连接字符串配置节。我们只需在配置文件中配置数据库连接信息,就能在程序中方便的访问数据库连接字符串信息,从而轻松支持程序运行时更改数据库连接信息。
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <connectionStrings> 4 <add name="AdoNet" connectionString="Data Source=localhost;User Id=sa;Password=11111111;Initial Catalog=Apollo;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient"/> 5 </connectionStrings> 6 </configuration>
当然,该步骤并不是必须的,在我曾经作为外援参加的某国内还算知名软件公司的一个项目中,就将数据库配置信息硬编码到DAL类库代码中的。
二、连接数据库
ADO.NET提供了Connection对象来进行数据库连接对象的创建,并建立数据库连接。需要注意的是,尽可能晚的打开连接是通常需要遵循的行为准则。
1 protected readonly string CONNECTION_STRING = ConfigurationManager.ConnectionStrings["AdoNet"].ConnectionString; 2 … 3 using (var connection = new SqlConnection(CONNECTION_STRING)) 4 { 5 connection.Open(); 6 … 7 }
三、执行数据操作命令
ADO.NET提供了Command对象来执行数据操作命令。Command对象的ExecuteNonQuery()方法用于执行命令并返回受影响的行数,该方法常用于更新、删除数据;ExecuteScalar()方法用于执行命令并返回查询所影响的结果集中的第一行第一列,该方法常用于添加数据并返回自动生成的自增长标识列值;ExecuteReader()用于执行命令并返回IDataReader类型的数据,该方法常用于以只读步进的方式进行数据查询。需要注意的是,ExecuteReader()方法读取数据全程需要保持数据库连接,如果想以断开数据库连接的方式一次性将数据查询并放入内存中随时使用,请使用DataAdapter对象将数据源适配为DataSet对象。
1 using (var command = connection.CreateCommand()) 2 { 3 command.CommandText = SQL_INSERT; 4 command.Parameters.Add(new SqlParameter("@ID", user.ID)); 5 command.Parameters.Add(new SqlParameter("@Name", user.Name)); 6 7 return command.ExecuteNonQuery() > 0; 8 }
四、关闭连接
当数据操作命令执行完成后,请千万记得关闭数据库连接,尽早关闭数据库连接通常也是我们需要遵守的行为准则。Connection对象提供了Close()方法供显示调用关闭连接;同时,Connection对象也实现了IDisposiable接口,使用using语句建立数据连接的情况下,关闭连接的问题就不用再费心了,而由CLR将代劳。
我是在研读PetShop源码时第一次接触到了SqlHelper的。严格地说,SqlHelper算不上一种数据访问技术,而仅仅是将ADO.NET技术进行了简单的封装,让它使用起来更简便些。但是不要因此而小看SqlHelper,它可以有效的提高你的编码效率,显著减少数据访问层代码量,并使之更易维护。SqlHelper仅适用于SQL Server数据库,微软提供了OdbcHelper、OleDbHelper和XmlHelper等封装类,实现对其他数据源的快速访问。
SqlHelper是一个静态封闭类,它提供了ExecuteNonQuery()、ExecuteScalar()、ExecuteReader()和ExecuteDataSet()静态方法,使得对数据的访问不再像使用ADO.NET那样需要一二三四步了,而是一步到位:
var result = SqlHelper.ExecuteNonQuery(CONNECTION_STRING, CommandType.Text, sql, parameters);
谈DAAB之前,需要先谈谈Enterprise Library(微软企业库)。Enterprise Library是微软Patterns & Practices团队为企业级应用开发人员提供的一个组件库,最新的版本为2010年4月发布的Enterprise Library 5.0,该版本最高支持VS2010开发环境 + .NET Framework 4.0框架。
Enterprise Library由一系列应用程序块所组成,包括数据访问(Data Access Application Block)、异常处理(Exception Handling Application Block)、日志(Logging Application Block)、数据验证(Validation Application Block)、依赖注入容器(Unity Application Block)、缓存(Caching Application Block)、密码应用(Cryptography Application Block)和安全(Security Application Block)。可以看出,Enterprise Library所包含的内容基本涵盖了企业应用开发所需涉及的技术的方方面面。
作为企业访问不可或缺的一部分,DAAB(Data Access Application Block)在Enterprise Library中的地位非常重要,同时也是最常用的一个应用程序块。它的技术架构如下图所示:
图3 DAAB技术架构图
Enterprise Library提供了一系列的工具,以帮助开发人员更方便的使用各程序块。DAAB的使用也非常简单,这里就不介绍相关工具的使用了,而是直接编码。
一、配置连接
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <configSections> 4 <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> 5 </configSections> 6 <dataConfiguration defaultDatabase="AdoNet" /> 7 <connectionStrings> 8 <add name="AdoNet" connectionString="Data Source=localhost;User Id=sa;Password=11111111;Initial Catalog=Apollo;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient"/> 9 </connectionStrings> 10 </configuration>
二、创建数据库对象
var database = DatabaseFactory.CreateDatabase();
三、数据持久化
使用Database对象的实例方法,进行数据持久化操作:
database.ExecuteNonQuery(CommandType.Text, SQL_INSERT);
NHibernate是由Java平台的Hibernate迁移到.NET平台的一个优秀的开源项目。虽然NHibernate在.NET平台上的地位远没有其老大哥Hibernate那么显赫,但是该项目自创建以来一直十分活跃,版本更新频繁,目前已更新至3.3.3版本。不少的商业软件选择NHibernate,说明其在.NET平台上还是有市场的。我曾经在一个项目中选择了Spring.NET + NHibernate的组合来实现依赖注入、面向方面编程和数据持久化,完全能应付企业级软件的开发需求。
NHibernate采用非侵入式的方式进行对象-关系映射,从而成就了其轻量级ORM技术的美名,这一点相信成为很多架构师钟爱他的重要理由。NHibernate技术架构如下图所示:
图4 NHibernate技术架构图
NHibernate的使用大致可以分为配置信息、编写映射文件和持久化数据几个步骤:
一、配置信息
NHibernate需要对配置文件作以下配置方可使用:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <configSections> 4 <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> 5 </configSections> 6 7 <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> 8 <session-factory> 9 <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property> 10 <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> 11 <property name="connection.connection_string">Data Source=localhost;User Id=sa;Password=11111111;Initial Catalog=Apollo;MultipleActiveResultSets=True;</property> 12 <mapping assembly="Apollo.Blog.EF.Chapter1.Domain" /> 13 </session-factory> 14 </hibernate-configuration> 15 </configuration>
二、映射文件
1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Apollo.Blog.EF.Chapter1.Domain" namespace="Apollo.Blog.EF.Chapter1.Domain"> 3 <class name="User" table="`User`" lazy="false"> 4 <id name="ID"> 5 <generator class="guid" /> 6 </id> 7 <property name="Name" type="string" not-null="true" /> 8 </class> 9 </hibernate-mapping>
三、数据持久化
1 var sessionFactory = new Configuration().Configure().BuildSessionFactory(); 2 using (var session = sessionFactory.OpenSession()) 3 { 4 session.Save(user); 5 session.Flush(); 6 }
LINQ(Language Integrated Query,语言集成查询)是一组用于C#和VB.NET语言的扩展,它允许编写C#或者VB.NET代码以查询数据库相同的方式操作内存数据。LINQ提供提供了丰富的类似SQL的查询语法,功能强大且容易上手。
LINQ技术通过提供程序扩展,可以实现对任何数据源的访问。常用的提供程序有LINQ to SQL、LINQ to XML、LINQ to Objects、LINQ to Entities、LINQ to Datasets、LINQ to ADO.NET。LINQ技术架构如下图所示:
图5 LINQ技术架构图
LINQ的使用大致包括连接配置、关系映射、上下文环境定义和数据持久化四步:
一、连接配置
LINQ的数据库连接配置与ADO.NET一样:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <connectionStrings> 4 <add name="AdoNet" connectionString="Data Source=localhost;User Id=sa;Password=11111111;Initial Catalog=Apollo;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient"/> 5 </connectionStrings> 6 </configuration>
二、侵入式关系映射
1 [Table(Name = "User")] 2 public class LinqUser : User 3 { 4 [Column(IsPrimaryKey = true)] 5 public override Guid ID { get; set; } 6 7 [Column] 8 public override string Name { get; set; } 9 }
三、上下文环境
1 internal class LinqContext : DataContext 2 { 3 public LinqContext(string connection) 4 : base(connection) 5 { 6 } 7 8 public LinqContext(IDbConnection connection) 9 : base(connection) 10 { 11 } 12 13 public Table<LinqUser> Users 14 { 15 get { return this.GetTable<LinqUser>(); } 16 } 17 }
四、数据持久化
1 using (var db = new LinqContext(CONNECTION_STRING)) 2 { 3 db.Users.InsertOnSubmit(user); 4 db.SubmitChanges(); 5 }
Entity Framework的全称为ADO.NET Entity Framework,是在ADO.NET上层实现的ORM封装,其技术架构如下图所示:
图6 Entity Framework技术架构图
Entity Framework的使用与LINQ一样,分为连接配置、关系映射、上下文环境定义和数据持久化四步:
一、连接配置
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <connectionStrings> 4 <add name="EntityFramework" providerName="System.Data.EntityClient" 5 connectionString="provider=System.Data.SqlClient; 6 provider connection string=" 7 Data Source=localhost; 8 User Id=sa; 9 Password=11111111; 10 Initial Catalog=Apollo; 11 Integrated Security=False; 12 MultipleActiveResultSets=True;"; 13 metadata=..\..\..\Apollo.Blog.EF.Chapter1.Domain\Edmx\Chapter1.ssdl| 14 ..\..\..\Apollo.Blog.EF.Chapter1.Domain\Edmx\Chapter1.csdl| 15 ..\..\..\Apollo.Blog.EF.Chapter1.Domain\Edmx\Chapter1.msl"/> 16 </connectionStrings> 17 </configuration>
二、关系映射
Entity Framework是通过定义概念模型(CSDL)、物理模型(SSDL)及两者的映射关系(MSL),实现对象与关系映射的。
SSDL映射文件内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Schema Namespace="Chapter1.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" 3 xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl" 4 xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"> 5 <EntityContainer Name="Chapter1StoreContainer"> 6 <EntitySet Name="User" EntityType="Chapter1.Store.User" store:Type="Tables" Schema="dbo" /> 7 </EntityContainer> 8 9 <EntityType Name="User"> 10 <Key> 11 <PropertyRef Name="ID" /> 12 </Key> 13 <Property Name="ID" Type="uniqueidentifier" Nullable="false" /> 14 <Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="40" /> 15 </EntityType> 16 </Schema>
CSDL映射文件内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Schema Namespace="Chapter1" Alias="Self" 3 xmlns="http://schemas.microsoft.com/ado/2008/09/edm" 4 xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" 5 xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" 6 xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"> 7 <EntityContainer Name="Chapter1" annotation:LazyLoadingEnabled="true"> 8 <EntitySet Name="User" EntityType="Chapter1.User" /> 9 </EntityContainer> 10 11 <EntityType Name="User"> 12 <Key> 13 <PropertyRef Name="ID" /> 14 </Key> 15 <Property Name="ID" Type="Guid" Nullable="false" /> 16 <Property Name="Name" Type="String" Nullable="false" /> 17 </EntityType> 18 </Schema>
MSL映射文件内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs"> 3 <Alias Key="Cdm" Value="Chapter1" /> 4 <Alias Key="Storage" Value="Chapter1.Store" /> 5 <EntityContainerMapping CdmEntityContainer="Chapter1" StorageEntityContainer="Chapter1StoreContainer"> 6 <EntitySetMapping Name="User"> 7 <EntityTypeMapping TypeName="Cdm.User"> 8 <MappingFragment StoreEntitySet="User"> 9 <ScalarProperty Name="ID" ColumnName="ID" /> 10 <ScalarProperty Name="Name" ColumnName="Name" /> 11 </MappingFragment> 12 </EntityTypeMapping> 13 </EntitySetMapping> 14 </EntityContainerMapping> 15 </Mapping>
三、上下文环境
Entity Framework提供了ObjectContext上下文环境,以对数据对象进行持久化操作。通过继承它,可以方便的实现自定义数据对象的访问。
1 internal class EntityContext : ObjectContext 2 { 3 public EntityContext() 4 : this("name=EntityFramework") 5 { 6 } 7 8 public EntityContext(string connectionString) 9 : base(connectionString, "Chapter1") 10 { 11 this.Users = CreateObjectSet<User>(); 12 } 13 14 public ObjectSet<User> Users { get; set; } 15 }
四、数据持久化
1 using (var db = new EntityContext()) 2 { 3 db.Users.AddObject(user); 4 db.SaveChanges(); 5 }
本文就.NET平台常用的几种数据访问技术的技术框架及使用方法进行了概要阐述,并针对每种技术提供一个数据对象的CRUD功能实现。考虑到上述各种技术的形态差异较大,在此就不对各技术间的异同作对比了,后续将对Entity Framework与其它常用的ORM技术做详细对比论述。下一篇文章将介绍如何使用Entity Framework快速开发数据访问应用程序