ADO.NET

ADO.NET是微软在.NET Framework中负责资料存取的类别库集，它是使用在COM时代奠基的OLE DB技术以及.NET Framework的类别库和程式语言来发展的，它可以让.NET上的任何程式语言能够连接并存取关联式资料库与非资料库型资料来源（例如XML，Excel或是文字档资料），或是独立出来作为处理应用程式资料的类别物件，其在.NET Framework中的地位是举足轻重，许多人将ADO.NET视为 ActiveX Data Objects (ADO) 的下一个版本，但其实它是一个全新的架构、产品与概念。
ADO.NET类封装在System.Data.dll中，并且与System.Xml.dll中的XML类集成。

**历史**

**发展缘起**
在1990年代初期，微软已经开发了许多的资料存取方式，像是ODBC架构、和Microsoft Access资料库交互使用的DAO物件、可以跨越网路存取资料的RDO以及让DAO元件可以存取ODBC资料来源的ODBCDirect技术等等，技术虽然多，但是却又各自为政，而且每个技术的重叠性也很高（像是ODBC有Microsoft Access的驱动程式）；RDO虽然可跨网路，但是ODBC的驱动程式中也有提供跨网路的功能（像是SQL Server和Oracle驱动程式），如此琳琅满目重叠性又高的技术群，让企业与开发人员在选择、学习与应用上产生了很多的困难。
1996年，适逢COM的出现，微软将资料存取的核心开始改写为以COM为基础的OLE DB，并且在它上面建立一个新的统一的资料存取的高层对象模型－ADO。ADO推出后顺利的取代了DAO和RDO，成为在Windows NT 4.0和Windows 2000作业系统上开发资料库应用程式的首选。它将OLE DB的物件模型进一步简化；由资料库厂商开发满足OLE DB接口的资料提供者（data provider，这个模式在此时奠基），而ADO本身则是与资料来源无关（data source independent）的对象结构，让它迅速的获得了使用ASP、Visual Basic、COM的开发人员的青睐。它能够顺利取代DAO与RDO，要关键在于ADO的与数据库服务器端/客户端的特性无关，这使得ADO通用性极好。然而ADO本身的架构仍然有缺陷（尤其是在开发网路应用程式时，最好的例子就是Recordset无法离线），这也是微软为何不在.NET Framework中继续使用ADO的主要原因。
1998年时，微软提出了一个下一代的应用程式开发框架（Application Framework）的计划，计划中包含了：- ASP+：改良与重新设计ASP技术，强化它的Web应用程式发展能力。- Storage+：发展新的资料库与物件导向之档案系统结构（用于SQL Server 8.0（即后来的SQL Server 2000）与NTFS），以及发展新一代的资料存取元件，并改良ADO本身的缺陷，让它更能够成为应用程式资料存取的核心功能。- COM+：改良COM和MTS，成为企业级应用程式开发的基础元件。
ADO+即为Storage+的一支。

**ADO.NET的前身：ADO+**
1998年起，因为Web应用程式的窜起，大大改变了许多应用程式的设计方式，传统的资料库连线保存设计法无法适用于此类应用程式，这让ADO应用程式遇到了很大的瓶颈，也让微软开始思考让资料集（Resultset，在ADO中称为Recordset）能够离线化的能力，以及能在用户端建立一个小型资料库的概念此概念的原型为In-Memory Database，又称IMDB，可在记忆体中运行的资料库，然而以当时的环境（2000年初，记忆体尚未跌到目前的价格水准），以及技术不成熟的情况下，这项技术在Windows 2000 RC2时被抽离。，这个概念就是ADO.NET中离线型资料模型（disconnected data model）的基础，而在ADO的使用情形来看，资料库连线以及资源耗用的情形较严重（像是Server-side cursor或是Recordset.Open会保持连线状态），在ADO.NET中也改良了这些物件，构成了能够减少资料库连线和资源使用量的功能。XML的使用也是这个版本的重要发展之一。
2000年，微软的Microsoft .NET计划开始成形，许多的微软产品都冠上.NET的标签，ADO+也不例外，改名为ADO.NET有相同遭遇的还有ASP+（改名为ASP.NET）。，并包装到.NET Framework类别库中，成为.NET平台中唯一的资料存取元件。

**架构**
ADO.NET由连线资料来源（connected data source）以及离线资料模型（disconnected data model）两个部份构成，这两个部份是相辅相成的。

**连线资料来源**
若没办法连线到资料库，则无法被称为资料存取元件。连线资料来源便是用来连接资料库（或是具有OLE DB资料来源提供者）的物件类别，由下列接口构成：- IDbConnection，负责与资料库的连线管理，包含连线字串（connection string），连线的开关，资料库交易的启始与连线错误的处理，所有的ADO.NET资料提供者都要实作此介面。
** Open()/Close()：开启与关闭资料库连线。
** BeginTransaction()：启动资料库交易，并回传一个IDbTransaction物件，以控制交易的结果。- IDbCommand，负责执行资料库指令（在大多数的案例中都是SQL指令），并传回由资料库中撷取的结果集，或是执行不回传结果集的资料库指令。
** ExecuteNonQuery()：执行不回传结果集的资料库指令，像是INSERT、UPDATE与DELETE指令，返回值为该命令所影响的行数。 对于其他所有类型的语句，返回值 为 -1。
** ExecuteScalar()：执行指令并回传第一列第一行中的值（object类型）。当没有数据时，ExcuteScalar方法返回System.DBNull。
** ExecuteReader()：执行指令并回传IDataReader物件，以读取资料集中的资料。
** BeginExecuteNonQuery：开始执行异步查询
** EndExecuteNonQuery: 结束执行异步查询- IDataParameter，负责装载资料库指令所需要的参数资料，在使用参数化查询时会经常使用。 对于不同的数据源来说，占位符不同。SQLServer数据源用@parametername格式来命名参数，OleDb以及Odbc数据源均用问号（?）来标识参数位置，而Oracle则以:parmname格式使用命名参数。- IDbTransaction，负责装载资料库交易所需的控制物件，以执行交易的认可（commit）或撤销（rollback）的工作。
** Commit()：认可资料库交易。
** Rollback()：撤销资料库交易。- IDbDataAdapter，负责将来自于IDbCommand执行取得的结果集，装载到离线型资料集（DataSet）或是离线型资料表（DataTable）中。
** Fill()：将资料填入离线型资料物件。
** Update()：将变更过的离线型资料物件中的资料写回资料库。- IDataReader，建立一个只可向前读取游标（forward-only）的资料读取器工具，以逐列读取方式存取资料，IDbDataAdapter内部也是由它来读取资料。
** Read()：第一次调用Read()方法获取第一行数据，并将游标指向下一行数据。当再次调用该方法时候，将读取下一行数据。当检测到不再有数据行时，Read()方法将返回false。- IDataRecord，在IDataReader读取资料后实际装载资料列的物件，提供方法来读取资料行中的资料，以及转换成.NET Framework原生型别的工具。
** GetOrdinal()：取得指定资料行的栏位索引值。
** IsDBNull()：判断指定栏位的资料是否为NULL值。
使用连线资料来源需要由开发人员自我管理连线，并且直接操作资料存取的相关细节，但它的优点是速度快，而且可以自订整个资料存取流程的逻辑。

**ADO.NET资料提供者(Data Provider)**
在.NET Framework中，ADO.NET预设提供了四种资料来源：- SQL Server：由System.Data.SqlClient提供原生资料来源，是微软官方建议存取SQL Server时建议使用的资料提供者。以Sql为字首的类别群- OLE DB Data Source：由System.Data.OleDb提供支援，可适用于OLE DB Provider for ODBC以外的OLE DB资料提供者。以OleDb为字首的类别群。支持本地事务和分布式事务。 对于分布式事务，默认情况下，用于 OLE DB 的 .NET Framework 数据提供程序会自动登记在事务中，并自动从 Windows 2000 组件服务获取事务详细信息。- Oracle：由System.Data.OracleClient提供支援，但使用者的电脑必须安装Oracle Client 8.1.7或更新版本才行（.NET Framework 1.1开始支援）。以Oracle为字首的类别群。必须同时引用 System.Data和 System.Data.OracleClient。- ODBC：补OLE DB Provider for ODBC的支援，由System.Data.Odbc提供支援（.NET Framework 1.1开始支援）。以Odbc为字首的类别群- EntityClient实体数据模型(EDM)应用程序的数据访问。使用 System.Data.EntityClient 命名空间。

其他厂商亦为不同的资料库提供资料来源：- DataDirect Technologiesdate=20060421221256 发行100%列管提供源，支援主流企业数据库（Oracle, Sybase, DB2, SQL Server, Progress RDBMS）- OpenLink Software给大量的客户指定数据库发行提供源，包括到其他资料存取机构的桥接提供源，并可以在视窗下微软自己或者Mono的CLR实做下支援。- MySQLdate=20080731003221 为本身的MySQL Database Server提供了ADO.NET的原生资料提供者。- Oracle自行开发的.NET Data Provider。
对每种Data Provider，ADO.NET要实现下述对象结构：
*Connection 对象提供与数据源的连接。
*Command对象使您能够访问用于返回数据、修改数据、运行存储过程以及发送或检索参数信息的数据库命令。
*DataReader 对象从数据源中提供快速的，只读的数据流。
*DataAdapter 对象提供连接 DataSet 对象和数据源的桥梁。DataAdapter 使用 Command 对象在数据源中执行 SQL 命令，以便将数据加载到 DataSet 中，并使对 DataSet 中数据的更改与数据源保持一致。
*Parameter 对象用于参数化查询。
例如，对于SQL Server数据源：
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)};
paras0.value = userName;//绑定用户名
paras1.value = password;//绑定用户密码
*ConnectionStringBuilder：提供一种用于创建和管理由 Connection 对象使用的连接字符串的内容的简单方法。 所有 ConnectionStringBuilder 对象的基类均为 DbConnectionStringBuilder 类。 
*CommandBuilder ：自动生成 DataAdapter 的命令属性或从存储过程中派生参数信息，并填充 Command 对象的 Parameters 集合。 所有 CommandBuilder 对象的基类均为 DbCommandBuilder 类。

**离线资料模型**
离线资料模型是微软为了改良ADO在网路应用程式中的缺陷所设计的，同时它也是COM+中，IMDB技术的设计概念的实作品，但它并没有完整的IMDB功能，像是交易处理（transaction processing），但它仍不失为一个能在离线状态下处理资料的好帮手，它也可以透过连线资料来源物件，支援将离线资料存回资料库的能力。离线资料模型由下列物件组成：- DataSet，离线型资料模型的核心之一，可将它当成一个离线型的资料库，它可以内含许多个DataTable，并且利用关联与限制方式来设定资料的完整性，它本身也提供了可以和XML交互作业的支援。
** ReadXml()/WriteXml()：以DataSet的结构读写XML。
** ReadXmlSchema()/WriteXmlSchema()：以DataSet的结构读写XML Schema。
** GetXml()/GetXmlSchema()：取得DataSet内容的XML或XML Schema。
** Merge()：合并两个DataSet。
** Load()：自IDataReader载入资料到DataSet。
** AcceptChanges()：将修改过的资料列的修改旗标改为Unchanged。
** GetChanges()：将修改过的资料列以DataRow阵列方式传回。
** RejectChanges()：撤销所有资料的修改。- DataTable，离线型资料模型的核心之一，可将它当成一个离线型的资料表，是储存资料的收纳器。
** Copy()：将DataTable复制出一个副本，包含结构与资料。
** Merge()：将两个DataTable合并。
** Select()：以指定的特殊查询语法，传回符合条件的DataRow阵列。
** Compute()：以指定的汇总语法，传回汇总的结果。
** GetErrors()：传回有错误的DataRow阵列。
** HasErrors：判断DataTable中的DataRow有没有含有错误的DataRow。- DataRow，表示表格中的资料列，与资料栏组合成资料储存的单元。
** IsNull()：判断指定的栏位是否为NULL值。
** ItemArray：将DataRow中的资料转换成阵列。- DataColumn，表示表格中的栏位。- DataView，展示资料的辅助元件，类似于资料库中的检视表，并可设定过滤条件与排序条件。
** Filter：设定DataView的过滤条件。
** Sort：设定DataView的排序条件。
** ToTable()：将套用过滤与排序后的内容转换为DataTable物件。- DataRelation，可在DataTable之间设定栏位间的关联。- Constraint，设定栏位的条件约束，例如ForeignKeyConstraint为外部键限制，而UniqueConstraint则确保了栏位中的值都是唯一的。
DataSet和DataTable除了资料库的处理以外，也经常被用来管理应用程式中的资料，并且由于它可以储存在XML中的特性，也让它可以用来储存需要保存的应用程式资讯。DataSet中可包含DataRelation对象，用于建构表之间的约束关系。

**工厂方法**
在.NET Framework 1.x的时代，ADO.NET不同的来源有不同的类别搭配（前面已述及），但是若想要在不同的资料来源间搭配，那么势必要产生很多的变数来存放不同的资料物件，因此微软在.NET Framework 2.0中提供了一个System.Data.Common命名空间，其中有各种必要物件的共用方法（例如连线是DbConnection，命令是DbCommand，读取器是DbDataReader，资料配接器是DbDataAdapter等），以及DbProviderFactory物件，用来总管资料存取的物件。
DbProviderFactories则是电脑中所有提供者的总管，开发人员可用DbProviderFactories.GetFactoryClasses()取得各个提供者的Invariant Name，再于呼叫DbProviderFactories.GetFactory()时传入指定提供者的Invariant Name即可取得DbProviderFactory，再利用下列方法取得共用物件：- DbProviderFactory.CreateConnection()- DbProviderFactory.CreateCommand()- DbProviderFactory.CreateParameter()- DbProviderFactory.CreateDataAdapter()
// This example assumes a reference to System.Data.Common.
static DataTable GetProviderFactoryClasses()
{
// Retrieve the installed providers and factories.
DataTable table = DbProviderFactories.GetFactoryClasses();
// Display each row and column value.
foreach(DataRow row in table.Rows)
{
foreach(DataColumn column in table.Columns)
{
Console.WriteLine(rowcolumn);
}
}
return table;
}

**XML的整合**
XML在ADO.NET中扮演了相当重要的地位，DataSet和DataTable都可以转换成XML或和XML之间交换资料，在DataTable的内部资料的变更记录，可以被输出到一个XML的格式，用来识别变更的情形，这个格式称为DiffGram，而且它可以直接读入DataTable之中（使用DataTable.ReadXml（）并用XmlReadMode.DiffGram当参数）。一个典型的DiffGram如下：


ALFKI
New Company


ANATR
Ana Trujillo Emparedados y Helados


ANTON
Antonio Moreno Taquera


AROUT
Around the Horn




ALFKI
Alfreds Futterkiste





DataSet与DataTable也支援直接读入XML Schema建立结构的能力，以及自行依XML的内容推断（inference）其结构的能力，下列程式码为由XML推断结构的程式：
DataSet dataSet = new DataSet();
dataSet.InferXmlSchema9("input_od.xml", new string "urn:schemas-microsoft-com:officedata");
DataSet和DataTable可以使用XmlDataDocument类别和XML DOM整合在一起，XmlDataDocument的角色就像一个桥接介面，并且作为DataSet和DataTable可使用XPath与XML DOM方式存取的方法。下列程式码即为使用XmlDataDocument和资料库中资料转换为XSLT输出的范例：
// Assumes connection is a valid SqlConnection.
connection.Open();
DataSet custDS = new DataSet("CustomerDataSet");
SqlDataAdapter customerAdapter = new SqlDataAdapter(
"SELECT * FROM Customers", connection);
customerAdapter.Fill(custDS, "Customers");
SqlDataAdapter orderAdapter = new SqlDataAdapter(
"SELECT * FROM Orders", connection);
orderAdapter.Fill(custDS, "Orders");
connection.Close();
custDS.Relations.Add("CustOrders",
custDS.Tables"Customers".Columns"CustomerID",
custDS.Tables"Orders".Columns"CustomerID").Nested = true;
XmlDataDocument xmlDoc = new XmlDataDocument(custDS); 
XslTransform xslTran = new XslTransform();
xslTran.Load("transform.xsl");
XmlTextWriter writer = new XmlTextWriter("xslt_output.html", 
System.Text.Encoding.UTF8);
xslTran.Transform(xmlDoc, null, writer);
writer.Close();
在.NET Framework中，DataSet被分为两类，一种是不会强制使用特别型态的DataSet，称为Untyped DataSet，使用上较方便，但没有强制的型别限制，另一种则是Typed DataSet，会强制型别，并且是由自订的XML Schema所产生，Untyped DataSet则没有XML Schema，由建立时的结构来决定，Typed DataSet可以用Visual Studio，或者是SDK工具中的xsd.exe来产生。
xsd.exe /d /l:CS XSDSchemaFileName.xsd /eld /n:XSDSchema.Namespace
产生出来的Typed DataSet会自动将栏位设定成属性，让开发人员的存取更方便（这个功能在TableAdapter相当常见）。
CustomerDataSet customers = new CustomerDataSet();
SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT * FROM dbo.Customers;",
"Data Source=(local);Integrated " +
"Security=SSPI;Initial Catalog=Northwind");
adapter.Fill(customers, "Customers");
foreach(CustomerDataSet.CustomersRow customerRow in customers.Customers)
Console.WriteLine(customerRow.CustomerID);

**指令产生器**
ADO.NET中有专门用来产生资料处理指令的指令产生器（Command Builder），它可以利用开发人员所指定的SELECT指令，自动产生对应的INSERT、UPDATE与DELETE指令，但一开始它并不会自动产生，而是要靠呼叫方法来取得：- DbCommandBuilder.GetInsertCommand()- DbCommandBuilder.GetUpdateCommand()- DbCommandBuilder.GetDeleteCommand()
最常使用到的地方是和DataAdapter并用时，但它要求传入的SELECT语句所选择的列集合中必须要有主键或者唯一键，否则无法产生，同时自动产生的指令因为判断条件很多，对效能可能会有些影响。

**Visual Studio的支援**
ADO.NET和Visual Studio开发工具几乎已经是无缝的整合了，开发人员可以利用Visual Studio来建立强型别（strong-typed）的DataSet，到了Visual Studio 2005时更能够在Windows Forms应用程式中使用TableAdapter（Typed DataSet和DataAdapter整合的产物）来开发应用程式（不会再看到DataAdapter，但使用上差不多）。Visual Studio在建立Typed DataSet时有提供视觉化介面的支援，以及资料库组态精灵（Database Configuration Wizard）来让开发人员以简单的设定方式来建立DataSet，部分开发人员也将TableAdapter和ASP.NET应用程式的ObjectDataSource控制项并用，亦得到不错的效果。
在.NET Framework 3.5中，微软特别为了DataSet和DataTable建立了LINQ Provider（称为LINQ to DataSet或LINQ to ADO.NET），让LINQ可以在DataSet或DataTable上使用，可以让原本在DataSet上的投资（程式码）得以继续使用并享有LINQ的便利性。

**ADO.NET和ADO的差异**
对于ADO的开发人员来说，最明显的变化在于以往ADO中的Recordset消失了，并且明确的分开为连线型的DataReader以及离线型的DataSet与DataTable，并且发展支援离线型资料来源的浏览工具DataView，这样的改变，让习惯使用ADO的VB/ASP开发人员会有某种程度的不习惯，同时让ADO.NET的学习会较ADO有较些许的复杂性，因此有部分新入门或是VB 6.0/ASP开发人员会在学习.NET Framework或是使用VB.NET开发应用程式时，在.NET Framework中使用ADO来连接资料来源。但在.NET Framework应用程式使用ADO的话，.NET Framework会因为要多一层ADO的COM与VB.NET使用的.NET之间的资料转换，会让应用程式效能有少部分的损耗。
下述示例，用ADO读取一个数据库中的表格，然后转为ADO.Net的数据，最后绑定到DataGridView控件中去显示：
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
cn = New ADODB.Connection
rs = New ADODB.Recordset
cn.Provider = "Microsoft.ACE.OLEDB.12.0;"
cn.ConnectionString = "D:\\budget2016.accdb;"
cn.Open()
'check whether the serial num exists in table cutter
Dim sSQL As String = "SELECT * FROM tPassword" 
rs.CursorLocation = ADODB.CursorLocationEnum.adUseClient
rs.Open(sSQL, cn, ADODB.CursorTypeEnum.adOpenKeyset, ADODB.LockTypeEnum.adLockReadOnly,_ 
ADODB.CommandTypeEnum.adCmdText)
Dim da As New System.Data.OleDb.OleDbDataAdapter()
Dim ds As New DataSet()
da.Fill(ds, rs, "tPassword")
'rs.Close()  'Cannot close 
rs.ActiveConnection = Nothing
cn.Close()
rs = Nothing
cn = Nothing
DataGridView1.DataSource = (ds.Tables("tPassword"))
ADO的RecordSet，相当于在客户机或服务器的一个逻辑上的数据表格，使用者可以通过cursor来定位/读写当前行，但不能重新对这些行重新排序，也不能对客户机上的多个RecordSet数据执行跨表的连接（join）查询。ADO.Net针对上述缺点，在客户机上实现了DataTable与DataSet，这是客户机内存中的一套（多个）数据表格，可以对其执行各种单表或多表的查询、修改、删除操作，既可以使用SQL语句，也可以使用LINQ子语言。
ADO与ADO.Net的共同点，在其内部都是使用SQL语句来对后台数据库操作。
内在实现上，ADO是基于COM技术，因此变体类型（Variant）无处不在，这是一种通用、万能类型；而ADO.Net是强类型的，并很好地支持数据库元数据与XML功能。

**ADO.NET的进化**
随着网路应用程式的进化，ADO.NET也随之做了许多的改变，但不变的是，ADO.NET的基础提供了强固的发展支援，这些进化的技术都是植基于ADO.NET的核心元件而来。
长久以来，程式设计师和资料库总是保持着一种微妙的关系，在商用应用程式中，资料库一定是不可或缺的元件，这让程式设计师一定要为了连接与存取资料库而去学习SQL指令，因此在资讯业中有很多人都在研究如何将程式设计模型和资料库整合在一起，物件关联对应（Object-Relational Mapping）的技术就是由此而生，像Hibernate或NHibernate都是这个技术下的产物，而微软虽然有了ADO.NET这个资料存取的利器，但却没有像NHibernate这样的物件对应工具，因此微软在.NET Framework 2.0发展时期，就提出了一个ObjectSpace的概念，ObjectSpace可以让应用程式可以用完全物件化的方法连接与存取资料库，其技术概念与NHibernate相当类似，然而ObjectSpace专案相当大，在.NET Framework 2.0完成时仍无法全部完成，因此微软将ObjectSpace纳入下一版本的.NET Framework中，并且再加上一个设计的工具（Designer），构成了现在的ADO.NET Entity Framework。
Entity Framework利用了抽象化资料结构的方式，将每个资料库物件都转换成应用程式物件（entity），而资料栏位都转换为属性（property），关联则转换为结合属性（association），让资料库的E/R模型完全的转成物件模型，如此让程式设计师能用最熟悉的程式语言来呼叫存取。而在抽象化的结构之下，则是高度整合与对应结构的概念层、对应层和储存层，以及支援Entity Framework的资料提供者（provider），让资料存取的工作得以顺利与完整的进行。
以往在发展像是AJAX应用程式时，伺服端总是需要设计一个HTTP介面埠（end point），通常都会使用Web Service来实作，但是随着Mashup应用程式的成长，若每次都要为一份（或一组）资料撰写Web Service或HTTP end point的话，对开发人员也是不小的负担，而且Web Service只支援XML/SOAP的资料格式，无法相容于Mashup应用程式常用的JSON资料格式，微软也发现未来的Silverlight应用程式也是会面临到相同问题。
当时刚好微软的ADO.NET Entity Framework也正在开发中，它的EDM能力刚好可以提供给WCF资料存取的能力，因此微软特别以ADO.NET Entity Framework为基础，开发一个专门提供HTTP端点资料服务的资料供应层，即为WCF Data Services。

**注释**

**参考文献**

**参见**
 ActiveX Data Objects（ADO）- Open Database Connectivity（ODBC）- OLE DB