5.1初识ADO.NET
5.1.1什么是ADO.NET
简单的讲,ADO.NET是一组允许.NET开发人员使用标准的,结构化的,甚至无连接的方式与数据交互的技术。对于ADO.NET来说,可以处理数据源是多样的。可以是应用程序唯一使用的创建在内存中数据,也可以是与应用程序分离,存储在存储区域的数据(如文本文件、XML、关系数据库等)。
具体来说,ADO.NET 对 Microsoft SQL Server 和 XML 等数据源以及通过 OLE DB 和 XML 公开的数据源提供一致的访问。数据共享使用者应用程序可以使用 ADO.NET 来连接到这些数据源,并检索、处理和更新所包含的数据。
作为.NET框架的重要组成部分,ADO.NET 类封装在 System.Data.dll 中,并且与 System.Xml.dll 中的 XML 类集成。当编译使用 System.Data 命名空间的代码时,需要引用System.Data.dll 和 System.Xml.dll。
5.1.2理清ADO.NET与ADO的关系
作为一个普通的缩略词,"ADO.NET”并只不是"ADO”的简单升级版本。严格的讲,ADO.NET和ADO是两种截然不同的数据访问方式。
ADO的全称是Activex Data Objects,它是早期(.NET还未实施)开发人员用来访问数据的组件。随着.NET的发展,ADO.NET顺其自然地以其显著的优越性逐步取代ADO。从技术层面讲,ADO使用OLE DB接口并基于微软的COM技术,而ADO.NET拥有自己的ADO.NET接口并且基于微软的.NET体系架构。
虽然大多数基于 .NET 的新应用程序将使用 ADO.NET 来编写,但 .NET 程序员仍然可以通过 .NET COM 互操作性服务来使用 ADO。
5.1.3认识ADO.NET最核心的组成部分
ADO.NET是对Microsoft ActiveX Data Objects (ADO)一个跨时代的改进,它提供了平台互用性和可伸缩的数据访问。由于传送的数据都是XML格式的,因此任何能够读取XML格式的应用程序都可以进行数据处理。事实上,接受数据的组件不一定要是ADO .NET组件,它可以是基于一个Microsoft Visual Studio的解决方案,也可以是任何运行在其它平台上的任何应用程序。
以前做数据库访问的时候,需要一直与数据库保持连接,直到获取完所有满足需要的数据之后才会断开数据库连接,这种数据库访问方式称之为连接式数据访问技术。相比于以前的连接式数据访问技术,ADO.NET除了提供连接式数据访问技术之外,还提供了另一种断开式解决方案,那就是在内存中模拟一个数据库,也就是内存中的数据库。
我们知道在实际的数据库技术中,每个数据库就是一个业务逻辑单元,一般来说这个数据库包含了实现一个应用软件或者一个网站所需要的全部数据。在这里数据库就是顶级对象,我们引用创建数据库时所用到的名词database来表示(因为创建数据库的SQL语句是create database),在一个数据库里可以包含有多个表(table)和视图(view),除此之外还可以包含有一些外键关系等。在一个表(table)或者视图(view)里可以包含多个列(column)和行(row)。
在ADO.NET中对上面提到的对象都在内存中进行了模拟,在内存中的数据库对象称之为DataSet,一个内存中的数据库(DataSet)可以包含多个在内存中的表(DataTable)和内存中的视图(DataView),并且也允许在表存在一些关系(DataRelation)。同时在一个内存中的表(DataTable)或者内存中的视图(DataView)中也允许存在行(DataRow)和列(DataColumn)。
物理数据库与内存数据库之间的各对象的对应关系如下:
图1
System.Data命名空间提供了不同的ADO.NET类,它们既分工明确,又相互协作地提供表格数据的访问服务。该类库包含两组重要的类:一组负责处理软件内部的实际数据(DataSet),一组负责与外部数据系统通信(Data Provider)。具体架构如下图所示:
图2 ADO.NET核心组件
DataSet 是 ADO.NET 的非连接(断开)结构的核心组件。DataSet 的设计目的很明确:为了实现独立于任何数据源的数据访问。因此,ADO.NET结构可以用于多种不同的数据源,用于 XML 数据,或用于管理应用程序本地的数据。DataSet 包含一个或多个 DataTable 对象的集合,这些对象由数据行和数据列以及主键、外键、约束和有关 DataTable 对象中数据的关系信息组成。
ADO.NET 结构的另一个核心元素是 .NET 数据提供程序(Data Provider)。具体包括:
Connection 对象提供与数据源的连接。
Command对象使您能够访问用于返回数据、修改数据、运行存储过程以及发送或检索参数信息的数据库命令。
DataReader 对象从数据源中提供快速的,只读的数据流。
DataAdapter 对象提供连接 DataSet 对象和数据源的桥梁。DataAdapter 使用 Command 对象在数据源中执行 SQL 命令,以便将数据加载到 DataSet 中,并使对 DataSet 中数据的更改与数据源保持一致。
严格地说,在.net类库中并没有Connection、Command、DataAdapter和DataReader对象的,这是对相关的对象做了一个抽象。在实际的开发中,我们经常用到的数据库有Access、SQL Server、Oracle、MySQL等,尽管大部分都遵循SQL国际化标准,但是它们在遵循标准的前提下又做了一些扩充,并且即使遵循了相同的标准,但是实现方法并不相同,所以在某些情况下实现相同的功能可能在不同的数据库中SQL语句并不相同。
于是,在ADO.NET也定义了一套用于访问数据库的标准,当然这个标准是以接口(interface)的形式提供的,各数据库厂商只要实现了这个接口就能在ADO.NET下正常工作(这也是接口的作用,接口就是用于指定规范,自己本身并不实现,在Java中针对数据库访问也有一套接口留待各数据库来实现)。当然在.net类库中微软已经提供对Access、SQL Server和Oracle数据库对上面提到的接口的实现。
在ADO.NET中定义的这一套接口是IDbConnection、IDbCommand、IDbDataAdapter和IDataReader,并且还有一套实现这些接口的抽象类,分别是DbConnection、DbCommand、DbDataAdapter和DataReader。
图3数据库访问接口
图4数据库访问抽象类
图5 SQL Server
图6 Access
图7 My Sql
图8 Oracle
5.1.4 ADO.NET扩展
提供一致的数据访问,是使用ADO.NET的一个关键的优势。但是对于开发人员来说,更大的优势是通过ADO.NET将管理的数据作为对象来说处理。 表中的每个字段都是强类型成员,与.NET 通用类型系统(Common Type System)完全兼容。个别的字段甚至可以作为局部变量来使用。数据行或者其他的数据集对象是标准的.NET 集合(Collections),可以用标准的迭代方法处理。
Entity Framework和LINQ是微软为了提高ADO.NET核心功能而建立的两个新的工具。需要注意的是,它们并不是ADO.NET的基本组成部分。
Entity Framework 利用了抽象化数据结构的方式,将每个数据库对象都转换成应用程序对象 (entity),而数据字段都转换为属性 (property),关系则转换为结合属性 (association),让数据库的 E/R 模型完全的转成对象模型。而在抽象化的结构之下,则是高度集成与对应结构的概念层、对应层和储存层,以及支持 Entity Framework 的数据提供者 (provider),让数据访问的工作得以顺利与完整的进行。
LINQ允许编写C#或者Visual Basic代码以查询数据库相同的方式操作内存数据。LINQ是一个通用的数据工具,可以让你非常容易地融合不同数据源的数据,并得到单一的数据结果集。
5.2了解.NET数据提供程序
5.2.1什么是.NET数据提供程序
.NET Framework数据提供程序用于连接数据库、执行命令和检索结果。这些结果将被直接处理,放置在 DataSet 中以便根据需要向用户公开、与多个源中的数据组合,或在层之间进行远程处理。.NET Framework 数据提供程序是轻量的,它在数据源和代码之间创建最小的分层,并在不降低功能性的情况下提高性能。
下表列出了 .NET Framework 中所包含的数据提供程序。
表1
5.2.2 .NET数据提供程序的核心对象
在上文中,我们知道Connection对象、Command对象、DataReader对象以及DataAdapter对象构成了.NET数据提供程序的骨架。这四个对象非常重要,在后续的文章中,我将详细的讲解。
5.2.3其他重要的对象
如果说上述四大对象构成了.NET数据提供程序的骨架,那么下面我要说的这些对象可以说是.NET数据提供程序的血肉了。这些对象虽然没有四大核心对象那么的光鲜耀眼,但却也是“八仙过海,各显神通”。
5.2.3.1 Parameter对象
说对Parameter对象,也许大部分人会说:“哦,原来是它啊”。尽管大部分人已经很熟悉了,我还是要在这里唠叨几句。我需要强调是,这一系列的文章主要写给对ADO.NET还不熟悉,或者刚入门的读者,旨在讲解ADO.NET最最基础却又非常重要的内容。
简单的讲,Parameter对象定义了命令和存储过程的输入、输出和返回值参数。哦!看起来,好像并不是那么强大,那么Parameter对象到底有什么本领呢?
先看这样一段代码,也许很多人曾经都写过,包括我自己:
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
只要懂一点SQL语法的童鞋都知道,这不就是一个简单的登陆查询验证代码吗?好,先别急,你知道的,说不定别人还不知道。
试想一下,如果用户(一般是那些技术高超,自称为“黑客”的高级用户)填入
userName = "' OR '1'='1";
与
passWord = "' OR '1'='1";
接下来,将见证奇迹的时刻:该用户竟然成功登陆网站了。哇!看起来这一切似乎多么的魔幻和神奇,其实我们稍作分析发现这也不过是一些雕虫小计。
我们将userName和passWord变量带入strSQL变量后,将得到这样的一条SQL语句:
strSQL = "SELECT * FROM users WHERE (name = '' OR '1'='1') and (pw = '' OR '1'='1');"
也就是实际上运行的SQL命令会变成下面这样的
strSQL = “SELECT * FROM users;”
到这一步,我想也不需要我多说了吧,你懂的!上面的情况,用专业术语来说就是一个简单的SQL注入(SQL injection)。记得上政治课的时候,我印象最深的一句话是,“万物都是矛盾统一的”。这句话经典而又真实,以至于时刻在我的脑海里浮现。有SQL注入的出现,因此就有参数化查询(Parameterized Query )的出现。
参数化查询是指在设计与数据库连结并存取资料时,在需要填入数值或资料的地方,使用参数 (Parameter) 来给值,这个方法目前已被视为最有效可预防SQL注入(SQL Injection) 的攻击手法的防御方式。在使用参数化查询的情况下,数据库服务器不会将参数的内容视为SQL指令的一部份来处理,而是在数据库完成 SQL 指令的编译后,才套用参数执行,因此就算参数中含有具破坏性的指令,也不会被数据库所执行。说了这么多,无非是想说明Parameter对象的重要性。黄婆卖瓜,也得自卖自夸一下吧!Parameter对象有两个非常重要的属性:DBType和Value。DBType用来设置或获取参数的类型,Value则用来设置或获取参数的值。
好了,现在我们用Parameter对象来改写简单的登陆验证代码:
strSQL = "SELECT * FROM users WHERE Name = @Name and Password = @Password";
SqlParamter[] paras = new SqlParamter[]{//参数数组
new SqlParamter("@Name",SqlDBType.Varchar,50)
new SqlParamter("@Password",SqlDBType.Varchar,50)};
paras[0].value = userName;//绑定用户名
paras[1].value = password;//绑定用户密码
5.2.3.2 两大得力助手:ConnectionStringBuilder和CommandBuilder
ConnectionStringBuilder:它提供一种用于创建和管理由 Connection 对象使用的连接字符串的内容的简单方法。 所有 ConnectionStringBuilder 对象的基类均为 DbConnectionStringBuilder 类。
CommandBuilder :它自动生成 DataAdapter 的命令属性或从存储过程中派生参数信息,并填充 Command 对象的 Parameters 集合。 所有 CommandBuilder 对象的基类均为 DbCommandBuilder 类。
5.2.4理解.NET数据提供程序
5.2.4.1 用于 SQL Server 的 .NET Framework 数据提供程序 (SqlClient)
用于 SQL Server 的 .NET Framework 数据提供程序 (SqlClient) 使用自己的协议与 SQL Server 进行通信。 它是轻量的且性能良好,因为它进行了优化,可直接访问 SQL Server,而无需添加 OLE DB 或开放式数据库连接 (ODBC) 层。 下图4.1.1将用于 SQL Server 的 .NET Framework 数据提供程序与用于 OLE DB 的 .NET Framework 数据提供程序进行对比。 用于 OLE DB 的 .NET Framework 数据提供程序通过 OLE DB 服务组件(它提供连接池和事务服务)和用于数据源的 OLE DB 访问接口与 OLE DB 数据源进行通信。
图9 SQL Server 与 OLE DB .NET Framework 数据提供程序进行对比
若要使用用于 SQL Server 的 .NET Framework 数据提供程序,您必须具有对 SQL Server 7.0 或更高版本的访问权限。 用于 SQL Server 类的 .NET Framework 数据提供程序位于 System.Data.SqlClient 命名空间中。 对于早期版本的 SQL Server,请将用于 OLE DB 的 .NET Framework 数据提供程序与 SQL Server OLE DB 访问接口 System.Data.OleDb 一起使用。
用于 SQL Server 的 .NET Framework 数据提供程序支持本地事务和分布式事务。 对于分布式事务,默认情况下,用于 SQL Server 的 .NET Framework 数据提供程序会自动登记在事务中,并自动从 Windows 组件服务或 System.Transactions 获取事务详细信息。
如果你使用SQL Server数据提供程序需要引入:
using System.Data.SqlClient;
5.2.4.2 用于 OLE DB 的 .NET Framework 数据提供程序
用于 OLE DB 的 .NET Framework 数据提供程序 (OleDb) 通过 COM 互操作使用本机 OLE DB 来启用数据访问。 用于 OLE DB 的 .NET Framework 数据提供程序支持本地事务和分布式事务。 对于分布式事务,默认情况下,用于 OLE DB 的 .NET Framework 数据提供程序会自动登记在事务中,并自动从 Windows 2000 组件服务获取事务详细信息。用于 OLE DB 类的 .NET Framework 数据提供程序位于 System.Data.OleDb 命名空间中。
如果你使用OLE DB数据提供程序需要引入:
using System.Data.OleDb;
5.2.4.3 用于 ODBC 的 .NET Framework 数据提供程序
用于 ODBC 的 .NET Framework 数据提供程序 (Odbc) 使用本机 ODBC 驱动程序管理器 (DM) 来启用数据访问。 ODBC 数据提供程序支持本地事务和分布式事务两者。 对于分布式事务,默认情况下,ODBC 数据提供程序会自动登记在事务中,并自动从 Windows 2000 组件服务获取事务详细信息。用于 ODBC 类的 .NET Framework 数据提供程序位于 System.Data.Odbc 命名空间中。
如果你使用ODBC数据提供程序需要引入:
using System.Data.Odbc;
5.2.4.4 用于 Oracle 的 .NET Framework 数据提供程序
用于 Oracle 的 .NET Framework 数据提供程序 (OracleClient) 通过 Oracle 客户端连接软件启用对 Oracle 数据源的数据访问。 该数据提供程序支持 Oracle 客户端软件 8.1.7 版或更高版本。 该数据提供程序支持本地事务和分布式事务两者。
用于 Oracle 的 .NET Framework 数据提供程序要求系统上安装有 Oracle 客户端软件(8.1.7 版或更高版本),才能连接到 Oracle 数据源。
用于 Oracle 类的 .NET Framework 数据提供程序位于 System.Data.OracleClient 命名空间中,并包含在 System.Data.OracleClient.dll 程序集中。 当编译使用该数据提供程序的应用程序时,必须同时引用 System.Data.dll 和 ```
System.Data.OracleClient.dll。
如果你使用Oracle数据提供程序需要引入:
using System.Data;
using System.Data.OracleClient;
5.2.5选择合适的 .NET 数据提供程序
应用程序或者数据源不同,我们就需要选择不同的.NET数据提供程序。在此,微软官方已经给了我们很好的建议。
表2