65.9K
CodeProject 正在变化。 阅读更多。
Home

ADO.NET 程序员参考 - 第 16 章 - COM 互操作性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (7投票s)

2001 年 11 月 14 日

24分钟阅读

viewsIcon

137890

本章解释如何使用互操作性来整合现有非托管组件和其他经典 ADO 对象的功能。

标题 ADO.NET 程序员参考
作者 Dushan Bilbija、Paul Dickinson、Fabio Claudio Ferracchiati、Jeffrey Hasan、Naveen Kohli、John McTanish、Matt Milner、Jan D Narkiewicz、Adil Rehan、Jon D Reid
出版社 Wrox
出版日期 2001 年 9 月
ISBN 186100558X
价格 39.99 美元
页数 700

COM 互操作性

什么是 COM 互操作性 (Interop)?它为什么重要?当提到互操作性时,这些是一些可能首先想到的基本问题。如果您正在开发数据访问层,另一个问题会浮现在脑海中:当我们有 ADO.NET 来处理所有数据访问需求时,这会如何影响我们的组件?在本章中,我们将找到这些问题的一些答案,并了解如何在应用程序中使用互操作性。

互操作性使托管代码能够与非托管代码进行互操作,反之亦然。

在公共语言运行时 (CLR) 下运行的代码是托管代码,而在 CLR 之外运行的代码是非托管代码。例如,所有使用 .NET 框架创建的组件(程序集)都是托管代码,所有使用 Win32 API 开发的组件都是非托管代码。

.NET 框架引入了一种全新的应用程序开发和部署模型。我们不再需要考虑编程语言的选择,因为每种语言都以 Microsoft 中间语言 (MSIL) 格式生成相同的元数据。归根结底,元数据是如何生成的并不重要,它都将被即时 (JIT) 编译为相同的本机(例如 x86)代码。

如果我们仔细研究每个开发人员都必须考虑的所有演变变化,那么说我们将不得不忘掉所有旧概念并从头开始可能并非不正确。这是否意味着您在服务器端或中间层使用了相当长一段时间的所有旧应用程序和组件将在 .NET 框架中停止工作,或者不能在启用 .NET 的应用程序中使用?绝对不是。我们在旧的和经过测试的组件上的努力和投资根本不会浪费。这就是互操作性发挥作用的地方。

COM 互操作允许我们在 .NET 应用程序中利用现有的 COM(组件对象模型)组件,而无需以任何方式修改 COM 组件。随着 .NET 获得认可,我们将听到很多关于 .NET 框架中互操作性的信息。本章将首先简要概述互操作性,然后演示如何使用它来访问经典 ADO 组件,并利用 ADO.NET 尚未开发的功能来利用您的 .NET 数据访问组件。

互操作性使托管代码能够透明地调用非托管代码,并使非托管代码能够调用托管代码。实际上,这意味着我们不需要重写非托管组件即可与 .NET 应用程序协同工作,并且我们可以开发托管组件以与非托管组件协同工作。我们所需要做的就是利用 .NET SDK 提供的一些实用程序和工具,将现有应用程序与托管应用程序集成。我们将在本章后面介绍这些工具和实用程序。

在一个理想的世界中,我们可能希望将所有现有的非托管代码转换为托管代码。然而,在许多情况下,考虑到某些设计限制以及更重要的推出新应用程序的时间框架,这将是不切实际的。有时根本无法将非托管代码转换为托管代码:例如,如果源代码不可用。此外,我们可能希望实现特定类型的操作:例如,使用带有服务器端游标的记录集,这无法通过 ADO.NET 实现。在这两种情况下,互操作性都能通过启用经典 ADO 技术的使用来解决问题。此外,还有一些其他技术,补充 ADO,例如用于数据定义语言和安全的 Microsoft ActiveX 数据对象扩展 (ADOX)、Jet 引擎和复制对象 (JRO) 以及用于多维数据 (ADOMD) 的 ADO,这些技术不受 ADO.NET 支持。互操作性是将这些技术集成到托管应用程序中的唯一方法。因此,在将现有应用程序迁移到 .NET 时,互操作性将发挥非常重要的作用。

本章不讨论如何使用 ADO、ADOX、JRO 和 ADOMD 数据访问技术。如果您想了解更多信息,请查阅《Professional ADO 2.5 Programming》(Wrox Press,ISBN 1-861002-75-0)和《ADO 2.6 Programmer's Reference》(Wrox Press,ISBN 1-861004-63-X)。

垃圾回收

所有托管对象都由垃圾回收器回收,这意味着当某个对象不再需要,并且没有任何人持有对它的引用时,垃圾回收器 (GC) 会从托管内存堆中释放与它关联的内存。然而,我们正在讨论与非托管代码的互操作性。我们知道在 CLR 之外运行的代码没有 GC。那么被非托管组件使用的托管组件会发生什么?当非托管 COM 组件在托管代码中使用时,谁来释放它们的引用?

.NET 框架为托管代码和非托管代码的互操作执行以下操作:

  • 当托管对象从 CLR 封送(marshal)到非托管代码时,会创建一个 COM 可调用包装器 (CCW)

  • 当托管代码中引用非托管对象时,会创建一个运行时可调用包装器 (RCW)

这些包装器对象充当托管和非托管执行引擎之间的通道。这些对象充当代理,在托管对象和非托管对象之间进行调用封送。

当 COM 客户端调用托管对象时,运行时会为该对象创建一个新的托管对象和一个 CCW。对象的生命周期由该 CCW 管理。当 COM 客户端在此托管对象上调用 AddRef 时,CCW 会为它保留引用计数。因此,对于 COM 客户端而言,此托管对象看起来与常规 COM 组件完全一样。当在此对象上调用 Release 方法时,CCW 会递减其引用计数。当此托管对象没有未完成的引用时(换句话说,引用计数降至零),CCW 会在 CLR 中释放托管对象。在下一个垃圾回收周期中,此已释放的托管对象将从托管堆中移除。使用托管对象的 COM 客户端应遵循经典的 COM 引用计数规则,并且应为每个 AddRef 调用进行 Release 调用。如果非托管代码忘记释放托管代码,CCW 将继续持有该对象,垃圾回收器将永远不会回收它。

当托管代码创建非托管 COM 组件实例时,CLR 会为此组件创建 RCW 并维护其引用计数。每当创建此组件的新对象时,此 RCW 上的引用计数就会增加。当所有引用的对象都不再需要时,RCW 会释放对 COM 对象的引用,并在下一个垃圾回收周期中,此包装器 (RCW) 会被回收。

错误处理

在我们深入探讨互操作性在数据访问组件中的使用之前,我们应该关注一个重要方面。这就是互操作性中的错误处理机制。在现有的 COM 组件中,错误条件通过 HRESULT 代码传达给调用者。如果调用成功,组件返回 S_OK,否则返回错误代码(E_FAIL 或一些自定义代码)给调用者。

托管代码采用不同的方法。错误通过异常报告。如果组件想向调用者报告失败情况,它会抛出异常。此异常可以是 .NET 框架提供的异常类之一(例如,InvalidCastException、InvalidOperationException 等),也可以是用户定义的异常(派生自 System.Exception 类的类)。当 COM 组件返回错误时,互操作层将其转换为框架定义类之一,并将 HRESULT 代码存储在 System.Exception 类对象的 HResult 属性中。当托管代码抛出异常时,互操作层将其转换为 HRESULT 值。

因此,如果我们的托管代码正在调用非托管代码的方法,那么我们应该通过为将返回的错误提供异常处理程序来保护我们的实现。另一方面,如果我们要实现将被非托管代码使用的托管代码,我们应该在向调用者抛出异常之前将 HRESULT 代码存储在异常类的 HResult 属性中。

互操作工具

在 CLR 中运行的托管代码本身无法跨越 CLR 边界来调用非托管组件。托管代码对非托管代码定义的数据类型一无所知。.NET 框架完全是关于数据类型的,因此它需要某种机制来引用非托管代码中定义的数据类型。COM 组件在类型库中定义其类型,类型库可以存在于单独的文件(.TLB 文件)中,也可以作为资源嵌入到 DLL 或 EXE 文件中。.NET 组件的元数据包含在程序集文件中。.NET 框架提供了在 CLR 和 COM 数据类型信息之间进行转换的工具。

TLBIMP(类型库导入器)

这是一个命令行工具,可用于将 COM 组件类型库中包含的 coclass 和接口信息转换为 .NET 元数据信息。以下示例展示了此工具的最简单用法,将嵌入在 msado15.dll 中的 ADO 类型库转换为 .NET 元数据,定义相应的 CRL 类型:

C:\>tlbimp msado15.dll /out:ClassicADO.dll

第一个参数是包含 COM 类型库的文件名。此文件可以是 DLL、EXE、OLB、OCX 或 TLB 文件。第二个参数是可选参数,可用于微调工具的输出。在此例中,我使用了 /out 参数,它指定了输出文件的名称(在此例中为 ClassicADO.dll)。每个可选参数的详细解释超出了本书的范围。我们可以使用 /? 选项调用此工具,以查看所有支持的选项,以及每个选项的简要说明。以下屏幕截图显示了由 /? 参数生成的选项列表:

有关这些可选参数的详细说明,请参阅 .NET Framework SDK 文档中的“.NET Framework 工具”部分。

该工具将整个类型库转换为 .NET 元数据程序集。它不能用于类型库的部分转换。一旦我们创建了描述元数据的程序集,我们就可以丢弃源类型库文件。互操作操作不再依赖于源文件。在程序集中创建的 MANIFEST 将 coclass 的 GUID 保存为 GuidAttribute。CLR 使用注册表中的此信息创建 COM 对象的实例。

我们只需调用 new 运算符即可在托管应用程序中创建非托管对象。没有 CoCreateInstance,也没有 QueryInterface 调用。RCW 在后台处理所有这些步骤。它使用存储在为 COM 对象生成的元数据程序集的清单中的 GUID 调用 CoCreateInstance 方法。

TLBEXP(类型库导出器)

这个命令行工具是 TLBIMP 工具的对应工具。它将托管程序集中包含的元数据转换为类型库。以下示例演示了此工具的用法:

C:\>tlbexp myassembly.dll /out:unmancode.dll

与 TLBIMP 类似,此工具也支持一些可选参数。上面的示例使用 /out 参数指定输出文件的名称。每个可选参数的详细解释超出了本书的范围。我们可以使用 /? 选项调用此工具,以查看所有支持的选项以及每个选项的简要说明。以下屏幕截图显示了由 /? 参数生成的选项列表:

有关这些可选参数的详细解释,请参阅 .NET Framework SDK 文档中的“.NET Framework 工具”部分。

Visual Studio.NET

我们可以使用 Visual Studio 7.0 直接添加对已注册 COM 组件类型库的引用。它将生成包含与类型库中包含的类型对应的元数据信息的程序集。这类似于使用 TLBIMP 工具,其中 IDE 定义了可选参数。

我们可以通过在“添加引用”对话框中选择“COM”选项卡来添加对类型库的引用。这将显示系统上所有已注册的类型库列表。选择您要添加引用的类型库。这两个步骤将创建程序集并向项目添加引用。以下屏幕截图显示了项目中 ADO 2.7 类型库引用的添加:

 

类型库已添加,命名空间为 ADODB。

TypeLibConverter 类

这个类定义在 System.Runtime.InteropServices 命名空间中,提供了一种从 COM 类型库创建元数据信息的编程方式。这个类有两个方法:ConvertTypeLibToAssembly 和 ConvertAssemblyToTypeLib,它们执行类型转换。本书不讨论这些方法。有关更多详细信息,请参阅 .NET Framework 文档中关于此类的说明。

使用上述工具之一创建程序集后,在编译源代码时需要引用此程序集。根据使用的编程语言,我们可以按如下方式引用此程序集的命名空间:

VB.NET

Imports ClassicADO

C#

using ClassicADO;

如果您正在使用 Visual Studio 7.0,则无需执行任何手动步骤来添加对包含元数据信息的程序集的引用。对于命令行编译代码,请使用 /r 选项添加对 DLL 的引用。

VB.NET

vbc /r:ClassicADO.dll Foo.vb

C#

csc /r:ClassicADO.dll Foo.cs

托管应用程序中的 ADO

在过去的几年里,ADO 技术取得了长足的进步。大量的辛勤工作和投资已被投入到开发使用这种数据访问技术的健壮、可伸缩和可靠的组件中。.NET 数据访问类 OleDbDataAdapter 考虑到了这一点,并提供了 Fill 方法,该方法将 ADO Recordset 对象作为输入参数,并使用提供的数据填充 DataSet 对象。有关更多详细信息,请参阅 DataAdapter 章中 OleDbDataAdapter 类的 Fill 方法。

这是我们可以在托管应用程序中使用 ADO Recordset 对象的一种方法。我们可以使用 TlbImp 工具从现有 COM 对象导入类型库,这些 COM 对象具有返回 Recordset 对象的方法和属性。

考虑一个典型 COM 组件的案例,例如 Shop.dll,它有一个方法返回库存记录的 Recordset。首先,我们将使用 TlbImp 将类型库导入 .NET 程序集:

C:\>tlbimp /out:ShopObjects.dll Shop.dll

这将类型库导入到 ShopObjects.dll。接下来,将此托管程序集添加到托管应用程序中:

vbc /r:ShopObjects.dll /r:ClassicADO.dll ShopApplication.vb

 

csc /r:ShopObjects.dll /r:ClassicADO.dll ShopApplication.cs

 

然后,在我们的托管程序集中,使用 new 运算符将此非托管组件创建为常规 CLR 对象。

为 ADO 创建 .NET 元数据

在托管应用程序中使用经典 ADO 对象的第一步是建立对元数据文件的引用,该文件描述了它公开的所有接口。默认情况下,ADO 库文件 msado15.dll 位于以下文件夹中:

<System Drive>\Program Files\Common Files\System\Ado

我们将使用 TLBIMP 工具生成 .NET 兼容的程序集,我们将从托管代码中引用该程序集:

在上面的示例中,我们从根目录调用了 TLBIMP,而 msado15.dll 文件位于该文件夹中。我们之所以能够这样做,是因为我们已经在系统环境的路径变量中指定了 ADO 文件的文件夹位置。否则,TLBIMP 工具必须从与源文件相同的文件夹中调用。尽管源文件不在根文件夹中,但输出将创建在根文件夹 (C:/) 中,然后复制到我们想要引用此元数据程序集的任何文件夹中。本章后续部分中的所有示例都使用相同的方法设置系统环境的路径变量,从根文件夹调用 TLBIMP 工具。

此操作将生成一个 .NET 程序集 ClassicADO.dll,其中包含 ADO 库的元数据,命名空间为 ClassicADO。我们可以为在此命名空间范围内的所有 ADO 对象提供完全限定名,例如 ClassicADO.Connection、ClassicADO.Command 和 ClassicADO.Recordset。

对 ADO 程序集的引用

为了将 TLBIMP 工具生成的 ADO 程序集编译到托管代码中,我们将按如下方式添加引用:

VB.NET

vbc /t:library /r:ClassicADO.dll /out:EmployeeDB.dll EmployeeDB.vb

C#

csc /t:library /r:ClassicADO.dll /out:EmployeeDB.dll EmployeeDB.cs

使用记录集填充数据集

以下示例展示了在托管程序集中使用 ADO 对象。此示例需要使用服务器端游标支持,而 ADO.NET 不提供此支持。它通过提供连接字符串打开 ADO 连接,然后调用 Execute 方法获取 Recordset 对象。然后,它使用此 Recordset 对象通过 OleDbDataAdapter 类的 Fill 方法填充 DataSet。此方法也可用于获取带有服务器端游标的 Recordset 并将其传递给非托管代码。

VB.NET

Dim strQuery As String = "SELECT * FROM Employees WHERE EmployeeID = 1"
Dim strConn As String = "Provider=SQLOLEDB; Data Source = SYNCMONK" & _
                "; Initial Catalog = Northwind; User ID = sa; Pwd ="
                    
' Create a new instance ADO Connection object
Dim dbConn As New Connection ()
dbConn.CursorLocation = CursorLocationEnum.adUseClient

' Open the connection with specified connection string.
dbConn.Open (strConn, "", "", (int)ConnectModeEnum.adModeUnknown)

' Execute SQL query to get the emploee record.
Object recsAffected = Null
Dim rs Recordset = dbConn.Execute (strQuery, recsAffected,
                     CType (CommandTypeEnum.adCmdText, Integer))
               
Dim dtSet As New DataSet ()
Dim dtAdapter As New OleDbDataAdapter ()
dtAdapter.Fill (dtSet, rs, "Employee")
rs.Close ()

C#

string strQuery = "SELECT * FROM Employees WHERE EmployeeID = 1";
string strConn = "Provider=SQLOLEDB; Data Source = Northwind";
strConn += "; Initial Catalog = Northwind";
strConn += "; User ID = sa; Pwd = ;";
                    
// Create a new instance ADO Connection object
Connection dbConn = new Connection ();
dbConn.CursorLocation = CursorLocationEnum.adUseServer;
// Open the connection with specified connection string.
dbConn.Open (strConn, "", "", (int)ConnectModeEnum.adModeUnknown);

// Execute SQL query to get the employee record.
object recsAffected = null;
_Recordset rs = dbConn.Execute (strQuery, out recsAffected, (int)CommandTypeEnum.adCmdText);

DataSet dtSet = new DataSet ();
OleDbDataAdapter dtAdapter = new OleDbDataAdapter ();
dtAdapter.Fill (dtSet, rs, "Employee");
rs.Close ();

请注意,ADO 连接上的 Execute 方法调用的第二个参数已作为 System.Object 传递,而不是作为 VARIANT 传递。原因是 VARIANT 是一种非托管数据类型,互操作性将其封送为 System.Object。

每当我们不确定非托管方法参数应该使用什么数据类型时,最好使用 ILDASM 工具并查看为该方法生成的 IL 代码。TLBIMP 生成的 IL 代码可以提供关于方法调用需要传入的参数类型以及将返回何种输出的完整信息。IL 代码充当导入的非托管对象的文档。

托管应用程序中的 ADOX

ADO.NET 提供了一个用于操作现有数据源的框架。与经典 ADO 一样,它不直接支持创建新的数据源对象(例如物理数据文件)、维护用户和组以及处理权限对象。ADO 提供了 Microsoft ActiveX 数据对象扩展(用于数据定义语言和安全性的 ADOX)模型,这是一个扩展库,用于完成这些任务。但在 ADO.NET 中,对这些任务没有直接或间接的支持。这意味着我们必须依赖 Interop 调用 ADOX 库并执行这些操作。尽管 ADO.NET 为 SQL Server(版本 7.0 及更高版本)、OLE DB 驱动程序和本机 ODBC 驱动程序提供了托管提供程序,但通过 Interop 使用 ADOX 仍然受限于特定 OLE DB 驱动程序支持的 ADOX 功能。在当前可用的 OLE DB 驱动程序中,Microsoft Jet 完全支持 ADOX。SQL Server、Oracle、DB2 和其他 OLE DB 驱动程序提供了非常有限的 ADOX 实现。

为 ADOX 创建 .NET 元数据

在托管应用程序中使用 ADOX 的第一步是建立对元数据文件的引用,该文件描述了它公开的所有接口。ADOX 库文件名为 msadox.dll,默认位于您计算机的以下文件夹中。

<System Drive>\Program Files\Common Files\System\Ado

使用 TlbImp 工具生成 .NET 兼容的程序集,我们将从托管代码中引用该程序集:

此操作将生成一个 .NET 程序集 ADOX.dll,其中包含 ADOX 库的元数据,命名空间为 ADOX。我们可以为在此命名空间范围内的所有 ADOX 模型对象提供完全限定名,例如 ADOX.Catlog 和 ADOX.Table。

对 ADOX 程序集的引用

我们需要添加对 TLBIMP 工具生成的 ADOX 程序集的引用,以便将其与我们的托管代码一起编译。以下示例添加了对 ADOX.dll 的引用,以生成托管组件 EmployeeDB.dll。

VB.NET

vbc /t:library /r:ADOX.dll /out:EmployeeDB.dll EmployeeDB.vb

C#

csc /t:library /r:ADOX.dll /out:EmployeeDB.dll EmployeeDB.cs

创建 Microsoft Access 数据库文件

ADOX 中的 Catalog 对象可以通过调用 Create 方法来创建新的 Access 数据库。

以下代码演示了在托管程序集中使用 ADOX 元数据程序集创建新数据库。首先,它通过简单地调用 New 运算符创建一个 Catalog 对象的新实例。它实例化一个 Catalog 对象,然后在其上调用 Create 方法并传入连接字符串。连接字符串指定将使用 Jet4.0 OLE DB 提供程序创建新的 Microsoft Access 数据库文件。

VB.NET

Option Explicit On
Option Strict On
Imports System
Imports ADOX

Namespace ADOX_Interop
 Public Class ADOX_EmployeeDB

 Public Function CreateEmployeeDB () As Boolean

    Try
        Dim strConn As String = "Provider=Microsoft.JET.OLEDB.4.0;" & _
                         "Data Source = C:\\EmployeeDB.mdb"
        ' Create instance of Catalog object.    
        Dim dbCatalog As New Catalog ()
        ' Call Create method to create mdb file.
        dbCatalog.Create (strConn)
    Catch ex As System.Exception
        Console.WriteLine (ex.Message)
        Return False
    End Try
    Return True
  End Function
 End Class
End Namespace

C#

using System;
using ADOX;

namespace ADOX_Interop
{
  public class ADOX_EmployeeDB
  {
    public ADOX_EmployeeDB (){}
    public void CreateEmployeeDB ()
    {
      try
      {
       string strConn="Provider=Microsoft.JET.OLEDB.4.0;";
         strConn += "Data Source = C:\\EmployeeDB.mdb";
       // Create instance of Catalog object.
      Catalog dbCatalog = new Catalog ();
       //Call Create method to create mdb file.
         dbCatalog.Create (strConn);
     }
     catch (System.Exception ex)
     {
        Console.WriteLine (ex.Message);
        Return false;
     }
     return true;
     }
   }
}

在 Access 数据库中创建表

ADOX 中的 Catalog 对象公开 Tables 对象。Tables 对象公开 Columns、Index、Keys 和 Properties 集合。我们可以使用这些对象向数据库追加新表。

以下代码示例演示了如何使用 Catalog 对象创建 Microsoft Access 2000 数据库 .mdb 文件。首先,创建 Catalog 对象,然后将表添加到该对象。然后创建 Table 对象的新实例。这是所有数据列将添加到的对象。然后将新的数据列追加到 Table 的 Columns 集合中。Column 对象公开 Name、Attributes、DefinedSize、NumericScale、Precision 和 Type 等属性。为简洁起见,在此示例中,每个列仅指定了三个属性:名称、数据类型和大小。将列添加到集合后,通过调用 Append 方法将表追加到 Catalog 的 Tables 集合中。最后,将主键索引添加到表的 Indexes 集合中。Index 对象公开 Name、Unique、PrimaryKey 等属性,可用于控制其操作。

VB.NET

Dim strConn As String = "Provider=Microsoft.JET.OLEDB.4.0;" & _
                  "Data Source = C:\\EmployeeDB.mdb"
' Create instance of Catalog object             
Dim dbCatalog As New Catalog ()
dbCatalog.Create (strConn)

' Create instance of Table object
Dim dtTable As New Table ()
With dtTable
     .Name = "Address"
     .Columns.Append ("AddressID", ADOX.DataTypeEnum.adInteger, 4)
     .Columns.Append ("Street", ADOX.DataTypeEnum.adVarWChar, 128)
     .Columns.Append ("City", ADOX.DataTypeEnum.adVarWChar, 128)
     .Columns.Append ("State", ADOX.DataTypeEnum.adVarWChar, 128)
     .Columns.Append ("Zip", ADOX.DataTypeEnum.adVarWChar, 128)
     .Columns.Append ("Country", ADOX.DataTypeEnum.adVarWChar, 128)
End With

' Append the table to catalog.
dbCatalog.Tables.Append (CType(dtTable, Object))

' Create the primary key column with unique values.
Dim primKeyIdx As New Index ()
With primKeyIdx
     .Name = "UniqueAddrID"
     .Unique = True
     .PrimaryKey = True
     .Columns.Append ("AddressID", ADOX.DataTypeEnum.adInteger, 4)
End With
' Append the primary index to table.
dtTable.Indexes.Append (CType (primKeyIdx, Object), Nothing)

C#

string strConn = "Provider=Microsoft.JET.OLEDB.4.0;" +
            "Data Source = J:\\NetProjects\\EmployeeDB.mdb";
// Create instance of Catalog object.                  
Catalog dbCatalog = new Catalog ();
dbCatalog.Create (strConn);

// Create instance of Table object.                    
Table dtTable = new Table ();



dtTable.Name = "Address";
dtTable.Columns.Append ("AddressID", ADOX.DataTypeEnum.adInteger, 4);
dtTable.Columns.Append ("Street", ADOX.DataTypeEnum.adVarWChar, 128);
dtTable.Columns.Append ("City", ADOX.DataTypeEnum.adVarWChar, 128);
dtTable.Columns.Append ("State", ADOX.DataTypeEnum.adVarWChar, 128);
dtTable.Columns.Append ("Zip", ADOX.DataTypeEnum.adVarWChar, 128);
dtTable.Columns.Append ("Country", ADOX.DataTypeEnum.adVarWChar, 128);

// Append the table to catalog.
dbCatalog.Tables.Append ((object)dtTable);

// Create the primary key column with unique values.
Index primKeyIdx = new Index ();
primKeyIdx.Name = "UniqueAddrID";
primKeyIdx.Unique = true;
primKeyIdx.PrimaryKey = true;
primKeyIdx.Columns.Append ("AddressID", ADOX.DataTypeEnum.adInteger, 4);

// Append the index to table.
dtTable.Indexes.Append ((object)primKeyIdx, null);

我们可以通过互操作使用 ADOX 对象来增强托管应用程序的功能。在使用任何 ADOX 功能之前,务必查阅该特定 OLE DB 托管提供程序的文档,以检查哪些支持可用,哪些不可用。例如,使用 SQL Server 的 OLE DB 托管提供程序,我们无法像在经典 ADO 中使用 Catalog 对象的 Create 方法那样在数据库中创建新表。

以下示例演示如何使用 ADO 和 ADOX 对象在 SQL Server 数据库中创建新数据表。

VB.NET

Dim strConn As String = "Provider=SQLOLEDB;" & _
 "Data Source = DOTNET; Database = FashionHouse;" & _
 "User ID = foo; Pwd = ;"
' Create instance of Catalog object.
Dim dbCatalog As New Catalog ()
' Create ADO Connection object
Dim dbConn As New Connection ()
dbConn.Open (strConn)
dbCatalog.ActiveConnection = CType (dbConn, Object)

' Create instance of Table object.
Dim dtTable As New Table ()
With dtTable
     .Name = "Categories"
     .Columns.Append ("CategoryID", ADOX.DataTypeEnum.adInteger, 4)
     .Columns.Append ("Name", ADOX.DataTypeEnum.adVarWChar, 64)
     .Columns.Append ("Descr", ADOX.DataTypeEnum.adVarWChar, 128)
End With

' Append the table to catalog.
dbCatalog.Tables.Append (CType(dtTable, Object))

托管应用程序中的 JRO

ADO.NET 不直接支持诸如压缩数据库、数据库复制、副本同步、设置密码和数据库加密等功能。这些功能在经典 ADO 模型中通过 Jet 和复制对象 (JRO) 提供。应用程序可以通过使用互操作性访问 JRO 对象来提供所有这些功能。

为 JRO 创建 .NET 元数据

要建立对 JRO 库公开的所有接口的元数据文件的引用,我们将使用 TlbImp 工具在 msjro.dll 上创建 .NET 启用程序集。此库文件默认位于您计算机上的以下文件夹中:

<System Drive>\Program Files\Common Files\System\Ado

此操作将生成一个 .NET 程序集 JRO.dll,其中包含 JRO 库的元数据,命名空间为 JRO。我们可以为在此命名空间范围内的所有 JRO 模型对象提供完全限定名,例如 JRO.Replica 和 JRO.Filter。

对 JRO 程序集的引用

为了将 TLBIMP 工具生成的 JRO 程序集编译到托管代码中,我们将按如下方式添加引用:

VB.NET

vbc /t:library /r:JRO.dll /out:EmployeeDB.dll EmployeeDB.vb

C#

csc /t:library /r:JRO.dll /out:EmployeeDB.dll EmployeeDB.cs

复制 Access 数据库

以下示例展示了如何使用 JRO 副本对象创建 Microsoft Access 数据库的完整副本。

VB.NET

Dim strConn As String = "Provider=Microsoft.JET.OLEDB.4.0;" & _
                        "Data Source = C:\\EmployeeDB.mdb"
Dim strRep As String = "J:\\ EmployeeDB_Rep.mdb"
'Create an instance of Replica object
Dim dbRep As New Replica ()
dbRep.MakeReplicable ("J:\\NetProjects\\EmployeeDB.mdb", True)

' Call CreateReplica method to create replica of Employee database
dbRep.CreateReplica (strRep, "Replica of Employee Database", _
                     ReplicaTypeEnum.jrRepTypeFull)

C#

string strRep = "C:\\EmployeeDB_Rep.mdb";               
// Create an instance of Replica object
Replica dbRep = new Replica ();
dbRep.MakeReplicable ("C:\\EmployeeDB.mdb", true);
// Call CreateReplica method to create replica of Employee database
dbRep.CreateReplica (strRep, "Replica of Employee Database",
                     ReplicaTypeEnum.jrRepTypeFull,
                VisibilityEnum.jrRepVisibilityGlobal, -1,
                UpdatabilityEnum.jrRepUpdFull);

请注意,CreateReplica 方法的所有参数(ReplicaType、Visibility、Priority 和 Updatability)必须在 C# 中指定,而在 VB 中它们是可选的。这是否意味着为 VB 生成的托管代码与为 C# 生成的代码不同?在我回答这个问题之前,让我们看一下 TLBIMP 工具生成的方法签名:

.method public hidebysig newslot virtual
    instance void  CreateReplica(
           [in] string  marshal( bstr) replicaName,
                [in] string  marshal( bstr) description,
                [in][opt] valuetype JRO.ReplicaTypeEnum ReplicaType,
                [in][opt] valuetype JRO.VisibilityEnum Visibility,
                [in][opt] int32 Priority,
                [in][opt] valuetype JRO.UpdatabilityEnum updatability
          ) runtime managed internalcall
{
  .custom instance void
  [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)
     = ( 01 00 0E 00 02 60 00 00 )                         // .....`..
  .param [3] = int32(0x00000002)
  .param [4] = int32(0x00000001)
  .param [5] = int32(0xFFFFFFFF)
  .param [6] = int32(0x00000000)
  .override JRO.IReplica::CreateReplica
}

请注意 CreateReplica 方法的最后四个参数。每个参数都关联了两个属性。第一个是 [in],表示它是 INPUT 参数;第二个是 [opt],表示它是 OPTIONAL 参数。

Visual Basic 允许方法中缺少参数。在 VB 的情况下,框架会将缺少的参数替换为 Type.Missing 对象,这表示如果为这些参数指定了任何默认值,则使用这些值。C# 语言规范不允许使用缺少的参数。您必须显式指定这些可选参数的值,或为这些值提供 Type.Missing 对象。

Type.Missing 类定义在 System.Reflection 命名空间中。

托管应用程序中的 ADOMD

.NET 不支持操作联机分析处理 (OLAP) 服务器数据。经典 ADO 通过配套的 ADOMD 库提供了此支持。再次通过互操作,我们可以将 ADOMD 功能整合到我们的托管应用程序中。

为 ADOMD 创建 .NET 元数据

要建立对描述 ADO 库公开的所有接口的元数据文件的引用,请在 msado.dll 上使用 TlbImp 工具创建 .NET 启用程序集。此文件默认位于您计算机上的以下文件夹中:

<System Drive>\Program Files\Common Files\System\Ado

此操作将生成一个 .NET 程序集 – ADOMD.dll – 其中包含 ADOMD 库的元数据,命名空间为 ADOMD。我们可以为在此命名空间范围内的所有 ADOMD 对象提供完全限定名,例如 ADOMD.Catalog 和 ADOMD.CubeDef。如果您将 ADOX 对象与 ADOMD 对象一起使用,这些完全限定名将至关重要,因为两个命名空间中存在名称冲突:例如,两个命名空间都包含 Catalog 对象。

对 ADOMD 程序集的引用

为了将 TlbImp 工具生成的 ADOMD 程序集编译到托管代码中,请按如下方式添加引用:

VB.NET

vbc /t:library /r:ADOMD.dll /out:EmployeeDB.dll EmployeeDB.vb

C#

csc /t:library /r:ADOMD.dll /out:EmployeeDB.dll EmployeeDB.cs

枚举目录中的多维数据集

以下示例演示如何将 ADOMD 对象 Catalog、CubeDefs 和 CubeDef 与 ADO Connection 对象一起使用,以查找 SQL Analytical Server 2000 提供的 FoodMart 数据源中定义的所有多维数据集。

代码遵循以下步骤:

  1. 它通过指定连接字符串创建 ADO 连接的新实例。请注意,连接字符串中没有 InitialCatalog。这是因为每个 OLAP 服务器只有一个目录。

  2. 连接已与服务器打开。

  3. 创建 Catalog 对象的新实例,并将打开的连接设置为其 ActiveConnection 属性。

  4. CubeDefs 属性提供数据库中 CubeDef 对象的集合。

  5. 访问 CubeDefs 集合中的每个对象以打印其名称。

VB.NET

Dim strConn As String = _
    "Provider=msolap; Data Source = localhost; " & _
    "Initial Catalog = FoodMart 2000; User ID=sa; Pwd="
Dim dbConn As New ClassicADO.Connection ()
dbConn.Open (strConn)
Dim dtCatalog As New ADOMD.Catalog ()
dtCatalog.ActiveConnection = CType (dbConn, Object)

Dim cubes As ADOMD.CubeDefs = dtCatalog.CubeDefs
Dim cube As ADOMD.CubeDef
For Each cube in cubes
    Console.WriteLine (cube.Name)
Next

C#

string strConn = "Provider=msolap; Data Source = SULTAN;" +
    "Initial Catalog = FoodMart 2000; User ID =; Pwd=";
ClassicADO.Connection dbConn = new ClassicADO.Connection ();
dbConn.Open (strConn, "", "", (int)ConnectModeEnum.adModeUnknown);
ADOMD.Catalog dtCatalog = new ADOMD.Catalog ();
dtCatalog.ActiveConnection = (object)dbConn;

CubeDefs cubes = dtCatalog.CubeDefs;
Console.WriteLine ("Number of Cubes = {0}", cubes.Count.ToString ());
foreach (ADOMD.CubeDef cube in cubes)
{
    Console.WriteLine (cube.Name);
}

以上示例的输出如下:

Number Of Cubes = 3
Sales
Warehouse
Warehouse and Sales

上面的示例可以扩展为访问各种 CubeDef 对象中包含的 Dimension、Hierarchy、Level 和 Member 对象。以下 C# 代码可以添加到上面的示例中,以从 WarehouseCubeDef 的时间维度打印出信息:

ADOMD.CubeDef warehouse = cubes["Warehouse"];
ADOMD.Dimension timeDim = warehouse.Dimensions["Time"];
foreach (ADOMD.Level lvl in timeDim.Hierarchies[0].Levels)
{
    Console.WriteLine (lvl.Caption);
    foreach (ADOMD.Member mem in lvl.Members)
    {
     Console.WriteLine ("\t" + mem.Caption + "\t" + mem.UniqueName);
    }
}

以下屏幕截图显示了上述代码的输出。

摘要

在本章中,我们已经了解了如何使用互操作性来整合现有非托管组件和其他经典 ADO 对象的功能。这是 .NET 框架提供的一种非常强大的机制,旨在简化从非托管组件到托管程序集的迁移路径。

有些功能要求在现有平台上使用互操作。其中最重要的是事务服务。MTS 和 COM+ 基础设施是基于 COM 的。因此,如果我们想创建一个能够参与事务的托管组件,我们将不得不以使其可以注册为 COM 组件的方式来实现它。.NET 组件可以使用 REGASM 工具注册为本机 COM 对象。然后可以将组件安装在 COM+ 浏览器中。现在,.NET 组件可以通过使用互操作功能参与事务。调用必须跨越 CLR 边界,因此性能会受到影响。因此,只有在没有其他 .NET 选项可用时才应使用互操作。

版权和署名声明

本章摘自 Dushan Bilbija、Paul Dickinson、Fabio Claudio Ferracchiati、Jeffrey Hasan、Naveen Kohli、John McTanish、Matt Milner、Jan D Narkiewicz、Adil Rehan、Jon D Reid 合著的《.NET Programmer's Reference》,由 Wrox Press Limited 于 2001 年 9 月出版;ISBN 186100558X;版权所有 © Wrox Press Limited 2001;保留所有权利。

未经出版商事先书面许可,本章的任何部分不得以任何形式或任何方式(电子、静电、机械、影印、录音或其他方式)复制、存储在检索系统中或传播,但包含在评论性文章或评论中的简短引用除外。

© . All rights reserved.