My FAQ,最新最全的IT技术FAQ
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 未整理篇 | 技术讨论
  当前位置: > IBM专区 > DB2 > .NET technology
细看 DB2 Stinger .NET CLR 例程
作者:佚名 时间:2005-09-09 15:28 出处:互连网 责编:小渔
              摘要:细看 DB2 Stinger .NET CLR 例程
学习何时以及如何实现 .NET CLR 例程

级别: 初级

Gwyneth Evans
Software Developer, IBM Canada Inc.
2004 年 8 月

对于新接触 DB2 外部例程,或者刚开始用 .NET 公共语言运行库(CLR)编程语言对 DB2 数据库应用程序逻辑进行编程的 DB2® UDB™ 用户来说,本文是十分理想的读物。您将学习何为 .NET 公共语言运行库例程(CLR),创建这样的例程需要什么,如何创建它们,以及实现它们的最佳时机。此外,作者还介绍了实现细节的有关技巧,这将使您得到提高,并能快速、容易地开发 .NET CLR 例程。

DB2 .NET CLR 例程术语
在 DB2 中,.NET CLR 例程是指封装数据库和应用程序逻辑的外部例程,而应用程序逻辑是用受支持的 .NET CLR 编程语言来实现的。下列术语将有助于您了解 DB2 对 .NET CLR 例程的支持:

  • 例程
    指的是 DB2 数据库对象,其中包括应用程序逻辑以及通常与特定任务相关的数据库操作。DB2 支持创建三种类型的例程: 过程(procedure)函数(function)方法(method)。每种类型在目的、实现和调用方法上都各不相同;但是,它们共享一致的 DDL 语法,都运行于数据库服务器上,而且与在客户机应用程序中实现它们所包含逻辑相比较,它们的执行性能通常更好。

    • 可以从客户机应用程序、触发器、另一例程或 CLP 中直接调用过程。
    • 用户定义的函数扩展了 SQL 语句,可以在 SQL 语句的 column-list 中或支持表达式的地方引用这些函数。
    • 方法为结构化类型提供了类行为 – 不支持 .NET CLR 方法。

     

  • 外部例程
    指的是可以由用户创建,并用宿主编程语言(C、C++、Java™、OLE、COBOL 或 .NET CLR 语言)编写其例程主体的 DB2 例程,而这些程序将会被编译成库的形式。外部例程是通过执行针对特定类型例程的 CREATE 语句来创建的,并需要通过引用 EXTERNAL NAME 子句来指定包含该例程功能的库。这些库驻留在数据库服务器的文件系统中,而非驻留于 DB2 自身之中 – 因此,它们是外部的。所有外部例程都可以包含 SQL。当通过名称调用外部例程时,DB2 就装入并运行与该例程名关联的外部库。

  • .NET Framework
    指的是 Microsoft® 应用程序开发环境,由 CLR .NET Framework 类库构成,用于为开发和集成代码片提供一致的编程环境。

  • 公共语言运行库(CLR)
    用于所有 .NET Framework 应用程序的运行时解释器。

  • 中间语言(IL)
    一种通过 .NET Framework CLR 解释的字节码。所有 .NET 兼容语言的源代码都要编译成 IL 字节码。

  • 程序集
    包含 IL 字节码的文件。它既可以是一个库,也可以是一个可执行程序。

  • DB2 .NET 数据提供者
    DB2(R) .NET 数据提供者是 ADO.NET 接口的扩展,它允许 .NET 应用程序通过安全连接访问 DB2 数据库、执行命令和检索结果。

  • Microsoft Visual Studio Add-in
    可用于 Visual Studio 开发环境的一组 DB2 Development Center 工具。这些工具包括对使用 DB2 例程和其他数据库对象定义的支持。在使用之前,必须先向 Visual Studio 注册该插件(Add-in)。

  • 可以用任何语言来实现 CLR 例程,只要能将这些语言编译成 IL 程序集。这些语言包括:Managed C++、C#、Visual Basic 和 J# 等。

实现 CLR 例程的益处
创建引用 IL 程序集的过程和函数的方式与创建外部例程的方式完全相同。在下列情况中,您应该选择使用 .NET 语言实现外部例程:

  • 如果需要在例程中封装复杂逻辑,以便访问数据库或在数据库的外部执行动作。
  • 如果需要从以下地方调用封装的逻辑:多个应用程序、CLP、另一例程(过程、函数(UDF)或方法)或触发器。
  • 如果极其适应用 .NET 语言为该逻辑编写代码。

无论用何种语言来实现例程,实现例程的理由最终都十分相似:

封装应用程序逻辑
在有许多客户计算机,并且每台计算机都运行各种各样的数据库应用程序的环境中,有效使用例程可以简化代码重用、代码标准化和代码维护。

能够控制对数据库对象的访问
可以使用例程控制对数据库对象的访问。通常可能不允许用户发出特定的 SQL 语句,但是可以允许他们调用例程,而这些例程中包含了这些语句的特定实现。

减少网络通信量
在客户计算机上运行应用程序时,要将每条 SQL 语句分别从客户计算机发送到服务器计算机,并分别返回每条结果。这可能会导致较大的网络通信量。如果有一个可以识别包含繁重数据库活动和少量用户交互的作业,那么将该作业安装在服务器上就很有意义。通过在服务器上运行该作业,可以减少客户计算机和服务器计算机之间的网络通信量。DB2 例程就是按该方式在数据库服务器上运行。使用例程可以有效地减少网络通信量,提高整个客户机应用程序的性能。

减轻客户机上的处理负载
在客户计算机性能受到关注的环境中,例程是减小客户计算机依赖性的实用方法。应用程序调用例程之后,在数据库服务器上完成例程的处理,这样,就可以在允许应用程序利用数据库服务器的强大功能的同时缓解客户计算机的处理负载。

允许更快、更高效的执行
例程是数据库对象,因此与数据库管理器的关系比客户机应用程序更近一些。在某些类型的例程中,SQL 语句的性能可能比在客户机应用程序中执行要好得多。例如,NOT FENCED 例程与使用共享存储器进行通信的数据库管理器运行在同一个进程中。这使得例程比客户机应用程序更擅长传送 SQL 请求和数据,而客户机应用程序是使用 TCP/IP 协议进行通信的。

逻辑实现的互操作性
因为代码模块通常是由不同的程序员来实现的,而每位程序员都有使用不同编程语言的编程经验,而且因为一般最好能够在可能的地方重用代码,以节省开发时间和成本,所以 DB2 例程具有高度的互操作性。

  • 使用该编程语言编写的客户机应用程序可以调用使用不同编程语言实现的例程。例如,C 客户机应用程序就可以调用 .NET 公共语言运行库例程。
  • 一个例程可以调用另一例程,而不必管该例程的类型或实现语言。例如,Java 过程(一种例程)可以调用 SQL 标量函数(使用不同实现语言的另一种例程)。
  • 可以从运行于不同操作系统上的 DB2 客户机,调用在某一操作系统中的数据库服务器上创建的例程。

易于实现高级 .NET CLR 语言和数据提供者方法
许多 .NET 语言,包括 C# 和 Visual Basic,都提供了强数据形态(strong data typing)和简单的、基于方法的编程语言语法。加之由易于创建 DB2DataReaders 的类组成的易于使用的 .NET 数据提供者,以及简化 DB2 交互的命令对象,易于使用的理由就非常明显了。

随着人们越来越喜欢选择 Microsoft 的 Visual Studio .NET 语言为中小型企业实现 Web 应用程序或带有图形化用户界面的应用程序,您有理由利用现有的 .NET 语言技能,并以此为基础,将之应用于您的服务器端逻辑的开发。DB2 例程可以用不同语言在不同平台上实现,DB2 通过例程的这一互操作性使您可以灵活地、不断构建新的应用程序代码,而不必抛弃以前用其他语言编写的已有代码。

先决条件
要创建 DB2 .NET CLR 例程,首先必须满足以下先决条件:

  • 数据库服务器必须运行支持 Microsoft .NET Framework 的 Windows® 操作系统。
  • 必须在服务器上安装 .NET Framework,Version 1.1。可以单独使用 .NET Framework,也可以将它作为 Microsoft .NET Framework 1.1 Software Development Kit(SDK)的一部分使用。
  • 必须安装下列 DB2 版本:
    • 在服务器上:DB2 Stinger 或更高版本
    • 在客户机上:DB2 Version 7.2 或更高版本
  • 外部例程执行 CREATE 语句的权限。

 

开发环境
活跃的 Visual Studio .NET 开发人员不需要过多地偏离 Visual Studio .NET 开发环境。DB2 Development Center 包含用于每个 Microsoft Visual Studio 应用程序开发环境的 DB2 开发插件(Development Add-In),这些开发环境有 Visual Basic version 6、Visual InterDev version 6 和 Visual C++ version 6。这些插件使您易于访问 Development Center 向导和功能,从而使您易于创建、构建和测试用于 Visual Studio 应用程序的 DB2 存储过程和 UDF。通过这些插件,您还可以为 DB2 例程创建基于 ADO 的代码和对象,以便在您创建的应用程序中使用它们。

有关 Visual Studio .NET DB2 Add-in 支持的更多细节,请参阅 http://publib.boulder.ibm.com/infocenter/db2help/topic/com.ibm.db2.udb.doc/core/c0010836.htm。

除了使用支持例程开发的插件工具,还可以使用您最喜欢的编辑器和命令行。本文中的实例主要用来展示 .NET CLR 例程的内容和功能,它们也展示了如何通过命令行(Command Line)的方法构建和运行 .NET CLR 例程。

DB2 .NET 数据提供者支持
DB2 .NET 数据提供者附带了 DB2 .NET 数据提供者支持的参考文档。它展示了有关所有 DB2 .NET 数据提供者对象及其成员的详细信息。在 DB2 的安装过程中,要向 Microsoft(R) Visual Studio .NET 注册该文档。若要从 Microsoft Visual Studio .NET 中查看 DB2 .NET 数据提供者文档,请选择 Help 菜单选项的 Contents。一旦打开了帮助查看器(viewer),就可以通过 IBM(R) DB2 .NET Data Provider Help 过滤信息。

有关 DB2 .NET Data Provider 对象的更多信息和文档,请参阅 参考资料。

CLR 例程结构
与所有的外部例程一样,.NET CLR 例程也包含两个主要部分:

  • DDL,以 CREATE 语句的形式,定义了 DB2 系统目录表中的例程。
  • 源代码,实现了例程中的逻辑和数据库操作。

 

例程的 CREATE 语句给例程提供了名称、参数签名,并且定义了关于例程的某些关键特性,包括程序集的位置,而这些程序集中包含了用以实现例程主体的方法。在调用例程时,DB2 从 DB2 例程定义集合中解析出例程名,找到合适的程序集,装载它,并执行相应的方法。

开发 CLR 例程
可以将您选择的编程语言支持的编程逻辑置于例程中,并且可以包含例程类型中所支持的任何 SQL 语句。以下是从命令行创建 DB2 .NET CLR 所采用的高级步骤。

附带的步骤细节和实例展示了不同的例程实现。

  1. 用 CLR 支持语言编写例程逻辑代码
    1. 在源代码文件中,如果例程将执行 SQL,就要使用或导入 IBM.Data.DB2。
    2. 使用映射 DB2 SQL 数据类型的数据类型正确声明主机变量和参数。
      • 请参阅 映射 DB2 .NET 数据提供者数据类型的 DB2 SQL 数据类型
    3. 必须根据 .NET CLR 例程的参数需求,使用某种 DB2 支持的参数样式来声明参数。用于 UDF 的高速暂存(scratchpad)和 DBINFO 类被作为参数传递给 CLR 例程。
      • 请参阅 CLR 例程参数
    4. 过程可以向过程调用者返回结果集。这不是用参数,而是通过保持打开游标(DB2DataReader 对象处于打开状态)来指定的。
      • 请参阅 CLR 过程结果集
    5. 设置例程的返回值,如果需要的话。
      • CLR 用户定义的标量函数在返回之前必须返回一个值。
      • CLR 用户定义的表函数要求将返回码指定为表函数每次调用的输出参数。
      • CLR 过程不需要任何特殊的返回码或值。
  2. 构建代码以创建 IL 程序集
    • 请参阅 构建 DB2 .NET CLR 例程源代码
  3. 部署程序集
    • 将程序集复制到数据库服务器上的 DB2 函数目录中,这是存储与 DB2 例程相关的程序集或库的推荐位置。该函数目录为 %DB2INSTANCE%\SQLLIB\function ,其中 %DB2INSTANCE% 是关联当前活动数据库管理器的目录。若要查找更多关于函数目录的信息,请参阅 SQL Reference 文档中以下语句中的 EXTERNAL 子句: CREATE PROCEDURECREATE FUNCTION

      如果您愿意的话,您也可以将程序集复制到服务器上的另一目录中,但是为了成功调用该例程,您必须注意程序集的全限定路径名,因为下一步骤中需要它。

  4. 为例程执行 CREATE 语句
    • 动态或静态地执行 CREATE PROCEDURECREATE function ,以便通过向 DB2 系统目录表添加细节信息在 DB2 中定义一个例程,而这些细节是关于例程参数签名、依赖性、运行时限制和例程的相关权限的。按要求指定这些重要的非默认子句:
      • 指定例程名和参数签名。
      • 用值 CLR 指定 LANGUAGE 子句。
      • 用所支持的参数样式名指定 PARAMETER STYLE 子句,该样式是在例程代码中实现的(例如, GENERAL )。
      • 通过下列这些值中的一个,用与例程相关联的程序集名称指定 EXTERNAL 子句:
        • 例程程序集的全限定路径名。
        • 例程程序集相对于函数目录的相对路径名。

        默认情况下,DB2 通过函数目录中的名称查找程序集,除非 EXTERNAL 子句中指定了该库的全限定名或相对路径名。

      • 如果例程是一个过程,就用值 1 指定 DYNAMIC RESULT SETS ,它将向调用者返回一个结果集。
      • 您不能为 CLR 过程指定 NOT FENCED 子句。默认情况下,CLR 过程将被执行为 FENCED 过程。
      • 指定 EXECUTION CONTROL 子句,对例程的运行时动作施加限制。
  5. 如果指定的例程调用者没有SYSADM 或 DBADM 特权,就要授予他们 EXECUTE 特权来运行该例程。任何拥有 SYSADM 或 DBADM 特权的用户都可以授予 EXECUTE 特权。
  6. 调用 DB2 .NET CLR 例程。对于过程,使用 CALL 语句,并指定必要的参数值。而对于 UDF,则执行动态或静态 SQL 语句,其中包含了对该 UDF 的引用。有关的更多信息,请参阅下面的 例程调用。

映射 DB2 .NET 数据提供者数据类型的 DB2 SQL 数据类型
如果例程传递参数,或者包含带有 SQL 参数的 SQL 语句,为了完整地保存目标值的长度和精度,您就必须使用适当的 DB2 .NET 数据提供者来表示那些值。以下是 DB2 SQL 数据类型到 .NET CLR 支持数据类型的映射。

DB2 SQL 数据类型到 DB2 .NET 数据提供者数据类型的映射

DB2Type 枚举 DB2 数据类型 .NET 数据类型
SmallInt SMALLINT Int16
Integer INTEGER Int32
BigInt BIGINT Int64
Real REAL Single
Double DOUBLE PRECISION Double
Float FLOAT Double
Decimal DECIMAL Decimal
Numeric DECIMAL Decimal
Date DATE DateTime
Time TIME TimeSpan
Timestamp TIMESTAMP DateTime
Char CHAR String
VarChar VARCHAR String
LongVarChar(1) LONG VARCHAR String
Binary CHAR FOR BIT DATA Byte[]
VarBinary VARCHAR FOR BIT DATA Byte[]
LongVarBinary(1) LONG VARCHAR FOR BIT DATA Byte[]
Graphic GRAPHIC String
VarGraphic VARGRAPHIC String
LongVarGraphic(1) LONG GRAPHIC String
Clob CLOB String
Blob BLOB Byte[]
DbClob DBCLOB(N) String

CLR 例程参数
CLR 例程中的参数声明必须遵循 DB2 支持的参数样式中的一种样式,并且必须考虑例程所使用的特定 .NET 语言的参数关键字需求。如果例程将使用高速暂存(scratchpad)、dbinfo 结构,或者具有 PROGRAM TYPE MAIN 参数接口,则还要考虑其他附加细节。下面将解决这些问题。

CLR 例程支持的参数样式
每个例程都必须遵循特定的参数交换约定,通称参数样式,在创建例程时,必须在该例程的 CREATE 语句的 PARAMETER STYLE 子句中指定它。必须在外部 CLR 例程代码的实现中准确反映该参数样式。CLR 例程支持下列 DB2(R) 参数样式:

  • SQL (过程和函数支持)
  • GENERAL (仅过程支持)
  • GENERAL WITH NULLS (仅过程支持)
  • DB2SQL (过程和函数支持)

 

虽然这些参数样式目前都得到了支持,但参数样式 GENERAL 是优先选择的参数样式,并且是最快和最容易实现的。

CLR 例程参数空指示器
如果为 CLR 例程选择的参数样式需要为参数指定空(null)指示器,那么当参数样式调用空指示器向量时,会将空指示器作为 System.Int16 类型值传递给 CLR 例程,或者在 System.Int16[] 值中进行传递。

通过值和引用传递 CLR 例程参数
.NET CLR 的支持语言要求方法参数以表明了参数特定属性的关键字开头,例如,参数是通过值还是通过引用来传递的,它是仅为输入参数还是仅为输出参数。

参数关键字是特定于 .NET 语言的。例如,若要在 C# 中通过引用传递一个参数,那么其参数关键字就是 ref,而在 Visual Basic 中,引用参数是由关键字 byRef 表示的。关键字必须用于表示 SQL 参数用法(IN、OUT、INOUT),这是在例程的 CREATE 语句中指定的。

当将参数关键字施加到 DB2 例程中的 .NET 语言例程参数时,将应用下列规则:

  • C# 中不要用参数关键字声明 IN 类型的参数,而 Visual Basic 中则必须用关键字 byVal 来声明。
  • INOUT 类型的参数必须用特定于语言的关键字来声明,该关键字表示参数是通过引用传递的。在 C# 中,该关键字是 ref。而在 Visual Basic 中,该关键字为 byRef。
  • OUT 类型的参数必须用特定于语言的关键字来声明,该关键字表示该参数仅为输出参数。在 C# 中,该关键字是 out。而在 Visual Basic 中,该关键字为 byRef。例程在返回给调用者之前,必须总是给仅输出参数赋予一个值;否则,在编译时将发出一个 .NET 错误。

 

下面展示了一个例程的 C# 参数样式 SQL 过程原型,该例程返回单一的输出参数语言。

C# 参数样式 SQL 例程签名

            public static void Counter  (out String language,
            out Int16  languageNullInd,
            ref String sqlState,
            String funcName,
            String funcSpecName,
            ref String sqlMsgString,
            ref Byte[] scratchPad,
            Int32  callType);
            

显然,因为与输出参数语言有关联的额外空指示器参数 languageNullInd 的使用,参数样式 SQL 才得以实现。可以使用该参数传递 SQLSTATE、例程名、例程特定名称和可选的用户定义的 SQL 错误消息。要按照下列规则为参数指定参数关键字:

  • 在 C# 中不需要给仅为输入的参数指定参数关键字。
  • 在 C# 中,关键字‘out’表示该变量仅输出参数,而且调用者不要初始化其值。
  • 在 C# 中,关键字‘ref’表示由调用者初始化该参数,而且例程可以任意修改该值。

 

要了解该语言中的参数关键字,请参阅关于参数传递的 .NET 语言的特定文档。

参数的内存分配
DB2 控制所有参数的内存分配,并且维护例程中所有输入和输出参数的 CLR 引用。

过程结果集不需要参数标志符
在要将结果集返回给调用者的过程的过程声明中,不需要参数标志符。CLR 存储过程中未关闭的游标语句(实现为处于打开状态的 DB2DataReader 对象),将作为结果集返回给其调用者。

将 Dbinfo 结构作为 CLR 参数传递给例程
用于向例程间和从例程传递附加数据库属性参数的 dbinfo 结构,通过 IL dbinfo 类的使用得到 CLR 例程的支持。该类包含了 C 语言的 sqludf_dbinfo 结构中可找到的所有元素,除了与字符串有关的长度字段。每个字符串的长度可以用特定字符串的 .NET 语言长度属性找到。

要访问 dbinfo 类,只需在包含了例程的文件中包括 IBM.Data.DB2 程序集,并在所用参数样式所指定位置中向例程签名添加参数类型 sqludf_dbinfo。

作为 CLR 参数的 UDF 高速暂存
如果为用户定义的函数请求一个高速暂存(scratchpad),那么要将之作为指定大小的 System.Byte[] 参数传递给例程。

CLR UDF 调用类型或最后的调用参数
对于请求最后调用参数或表函数的用户定义函数,要将调用类型参数作为 System.Int32 数据类型传递给例程。

CLR 过程支持的 PROGRAM TYPE MAIN
.NET CLR 过程支持程序类型 MAIN。定义为使用 Program Type MAIN 的过程必须具有以下签名:


            void functionname(Int32 NumParams, Object[] Params)
            

构建 .NET CLR 例程源代码
.NET CLR 例程的源代码基本上与 .NET 应用程序相同,但是,将 .NET CLR 例程构建为由 CLR 来执行的 IL 程序集时,必须包括对 IBM.Data.DB2.dll 的引用,IBM.Data.DB2.dll 包含 DB2 .NET 数据提供者和例程支持。例如,若要用 .NET Framework Version 1.1 编译包含了外部例程方法的 C# 源文件,则需要下列命令:


            csc /out:%1.dll /target:library /debug /reference:%DB2PATH%\bin\netf11\IBM.Data.DB2.dll %1.cs
            

其中:

DB2PATH 是由 DB2 设置的环境变量,用以指定在何处安装 DB2。如果在 Visual Studio .NET IDE 中进行开发,它就会默认地为您设置该编译命令。如果从命令行进行工作,您可以用简单的批处理文件编译代码,并按照下列方式,将已编译的程序集复制到函数目录中。

用于构建 CLR 例程的批处理文件

            @echo off
            rem Builds C# routines (stored procedures and UDFs)
            rem Usage: bldrtn prog_name
            rem Note: To compile and run the stored procedure samples, you must have
            rem           Version 1.1 or later of the .NET Framework installed.
            rem           When using the .NET Framework Version 1.1 point to netf11
            set VERSION=netf11
            rem Compile the program.
            csc /out:%1.dll /target:library /debug /reference:%DB2PATH%\bin\%VERSION%\IBM.Data.DB2.dll %1.cs
            if exist "%DB2PATH%\function\%1.dll" goto delete else goto copydll
            :delete
            del "%DB2PATH%\function\%1.dll"
            goto copydll
            :copydll
            rem Copy the routine assembly data link library to the 'function' directory
            copy "%1.dll" "%DB2PATH%\function"
            @echo onroutine

对于 Visual Basic,只需将编译命令修改为:


            vbc /target:library /debug /libpath:%DB2PATH%\bin\%VERSION%
            /reference:%DB2PATH%\bin\%VERSION%\IBM.Data.DB2.dll
            /reference:System.dll /reference:System.Data.dll %1.vb
            

执行例程的 CREATE 语句
为了创建外部例程,必须编写并执行一条格式良好的 SQL 语句,其中定义了该例程,例如 CREATE PROCEDURECREATE FUNCTION 语句。这些语句在 DB2 的系统目录表中创建了一个 DB2 对象,该表附带有名称、参数样式和规范,以及关于例程的特征的细节,这些特征允许 DB2 惟一识别例程,检查它是否已经被篡改,在调用时是否正确找到并装入与该例程相关的外部库,并根据提供的特征运行它。

以下是用于外部例程的 CREATE PROCEDURE 语句语法。关于 CREATE FUNCTION 语句的语法以及执行这些语句所需的权限,请参阅 SQL Reference(请参阅 参考资料)。

CREATE PROCEDURE 语句

            >>-CREATE PROCEDURE--procedure-name----------------------------->
            >--+--------------------------------------------------------+--->
            '-(--+----------------------------------------------+--)-'
            | .-,----------------------------------------. |
            | V .-IN----.                                | |
            '---+-------+--+----------------+--data-type-+-'
            +-OUT---+  '-parameter-name-'
            '-INOUT-'
            >--*--+-------------------------+--*---------------------------->
            '-SPECIFIC--specific-name-'
            .-DYNAMIC RESULT SETS 0--------.     .-MODIFIES SQL DATA-.
            >--+------------------------------+--*--+-------------------+--->
            '-DYNAMIC RESULT SETS--integer-'     +-NO SQL------------+
            +-CONTAINS SQL------+
            '-READS SQL DATA----'
            .-NOT DETERMINISTIC-.     .-CALLED ON NULL INPUT-.
            >--*--+-------------------+--*--+----------------------+--*----->
            '-DETERMINISTIC-----'
            .-OLD SAVEPOINT LEVEL-.
            >--+---------------------+--*--LANGUAGE--+-C-----+--*----------->
            '-NEW SAVEPOINT LEVEL-'               +-JAVA--+
            +-COBOL-+
            +-CLR---+
            '-OLE---'
            >--EXTERNAL--+----------------------+--*------------------------>
            '-NAME--+-'string'---+-'
            '-identifier-'
            .-FENCED------------------------.
            >--+-------------------------------+--*------------------------->
            +-FENCED--*--+-THREADSAFE-----+-+
            |            '-NOT THREADSAFE-' |
            |                .-THREADSAFE-. |
            '-NOT FENCED--*--+------------+-'
            .-EXTERNAL ACTION----.  .-INHERIT SPECIAL REGISTERS-.
            >--+--------------------+--+---------------------------+--*----->
            '-NO EXTERNAL ACTION-'
            >--PARAMETER STYLE--+-DB2GENERAL---------+--*------------------->
            +-DB2SQL-------------+
            +-GENERAL------------+
            +-GENERAL WITH NULLS-+
            +-JAVA---------------+
            '-SQL----------------'
            >--+------------------------------+--*-------------------------->
            '-PARAMETER CCSID--+-ASCII---+-'
            '-UNICODE-'
            .-NO DBINFO-.
            >--+------------------------+--*--+-----------+--*-------------->
            '-PROGRAM TYPE--+-SUB--+-'     '-DBINFO----'
            '-MAIN-'
            >--+---------------------------------------------------+-------><
            +-EXECUTION CONTROL-+----SAFE----+------------------+
            +---FILEREAD-+
            +--FILEWRITE-+
            +---UNSAFE---+
            

虽然看上去十分冗长而且很复杂,但实际并非如此。该语句提供了默认和非默认子句。横线上方列举了默认的子句。如果您希望使用非默认的值,只需显式地在 CREATE PROCEDURE 语句中包括该子句即可。

该语句中对于 .NET CLR 例程具有特殊意义,值得注意的子句有:

  • LANGUAGE
    该子句用于告诉 DB2 外部库是用何种语言实现的。对于包括 managed C 在内的 .NET CLR 支持语言,您都必须使用:CLR。
  • DYNAMIC RESULT SETS
    该子句表示过程是否返回结果集。.NET CLR 例程可以在此时仅返回一个结果集。
  • MODIFIES SQL DATA
    DB2 使用该子句确定允许例程访问 SQL 的目标级别。您应将之设置为适合于例程逻辑的最低访问级别,以便 DB2 可以检查并警告您外部库中某个库的 SQL 访问发生了改变。默认设置是最为宽松的限制设置。
  • EXTERNAL NAME
    该子句指定路径、名称空间、类和包含 .NET CLR 例程实现的方法名。最好指定全限定路径名,并在 SQLLIB\function 中存储与 .NET CLR 例程相关的程序集。SQLLIB\function 是 DB2 查找例程程序集的默认目录。但是,您也可以在 PATH 中所指定的任何目录中存储该程序集。指定要使用的外部方法的字符串必须为下列格式:
    
                    >>-'--assembly--:--class_id--!--method_id--'-------------------><
                    

    其中:

    • assembly: 指定 DLL 或类所驻留的程序集文件的全名
    • class_id: 指定要调用的方法所驻留的类名。如果该类使用了名称空间,那么它必须充当类名的前缀
    • method_id: 指定要调用的类中的方法

     

    单引号、对象标识符和分隔符之间不允许存在首尾空白字符(例如,' <a> ! <b> ' 是无效的)。但是,只要平台允许,路径名和文件名可以包含空白。对于所有文件名,既可以用其简写形式(例如:math.dll),也可以用全限定路径名(例如:d:\udfs\math.dll)来指定该文件。如果使用了该文件名的简写形式,若是平台为 UNIX 或者例程为 LANGUAGE CLR 例程,那么该文件必须驻留在函数目录中。如果该平台为 Windows,且例程并非 LANGUAGE CLR 例程,那么该文件必须驻留在系统 PATH 中。文件名中通常应该包含文件扩展名(例如:.a(UNIX 上)、.dll(Windows 上))。

  • PARAMETER STYLE
    DB2 支持一组参数样式,其作用稍有不同。在这里,我们为 .NET CLR 过程优先选取参数样式 GENERAL,但是,您也可以使用下面参数样式中的一种: GENERALGENERAL WITH NULLSSQL 。所有用户定义的函数都必须用参数样式 SQL 来实现,因为是从 SQL 语句中调用它们的。
  • EXECUTION CONTROL
    指定在运行时允许例程执行哪些类型的动作。在运行时,DB2 可以检测例程是否尝试执行超出其指定的执行控制模式范围的动作,这有助于确定一个库是否受到损害。

    这些模式展示了允许动作的层次结构,而高一级的模式包含该层次结构中位于它下面的层次所允许的动作。例如,执行控制模式 NETWORK 允许例程访问网络上的文件、本地文件系统中的文件和由数据库管理器控制的资源。

CLR 过程结果集
您可以开发 CLR 过程,将结果集返回给调用例程或应用程序。CLR 函数(UDF)无法返回结果集。

DB2 .NET 数据提供者是结果集的表示,它是一个 DB2DataReader 对象,可以通过调用各种 DB2Command 对象中的一种来返回该对象。而且可以将某些 DB2DataReader 对象作为结果集返回,在返回结果集之前,任何对象都不必显式调用其 Close() 方法。不必为返回结果集在函数定义中附加其他参数。

从 CLR 过程中返回结果集:

  1. 在 CLR 例程的 CREATE PROCEDURE 语句中,用等于该过程将要返回的结果集数目的值指定 DYNAMIC RESULT SETS 子句。在过程声明中,无需为返回给调用者的结果集指定参数标志符。
  2. 在 CLR 例程的 .NET 语言实现中,创建一个 DB2Connection 对象、一个 DB2Command 对象和一个 DB2Transaction 对象。 DB2Transaction 对象负责回滚和提交数据库事务。
  3. DB2Command 对象的 Transaction 属性初始化为 DB2Transaction 对象。
  4. DB2Command 对象的 CommandText 属性指派一个查询字符串,该属性定义了需要返回的结果集。
  5. 实例化 DB2DataReader ,并将 DB2Command 对象的 ExecuteReader 方法调用结果指派给它。该查询的结果集将包含在 DB2DataReader 对象中。
  6. 在将过程返回给调用者之前,不要执行 DB2DataReader 对象的 Close() 方法。一直打开的 DB2DataReader 对象将作为结果集返回给调用者。

 

在编译和部署了例程代码,并成功执行 CREATE PROCEDURE 语句之后,就可以用 CALL 语句调用该过程,查看返回给调用者的结果集了。

注意:
在发表本文时,您还无法用大于 1 的值来指定 DYNAMIC RESULT SETS

故障检修技巧
所有的外部例程通常共享一个公共实现,但是,可能会出现某些特定于 CLR 例程的 DB2 错误。无论 DB2 何时发出 DB2 例程的相关错误,错误消息文本都会详述错误原因和用户应该采取的用来解决该问题的行动。 db2diag.log 诊断日志文件中可以找到附加的例程错误场景信息。

为了避免 CLR 例程创建时或运行时的错误,要验证:

  • 当前是否不支持 .NET CLR 方法
  • 确保使用简写名(例如: math.dll )或全限定路径名(例如:d:\udfs\math.dll)正确地指定了 EXTERNAL NAME 子句。
  • 如果使用的是文件名的简写形式,则应确保该文件驻留在函数目录中。如果平台为 Windows,且例程并非 LANGUAGE CLR 例程,那么该文件必须驻留在系统 PATH 中。
  • 确保程序集驻留在 EXTERNAL NAME 子句所指定的目录中。
  • 确保将 DB2 实例正确配置为运行 .NET 过程或函数( mscoree.dll 必须出现在系统 PATH 中)。确保 db2clr.dll 包含在 sqllib/bin 目录中,以及全局程序集缓存器中安装了 IBM.Data.DB2。如果这些都不存在,则应确保数据库服务器上安装了 .NET Framework version 1.1 或更新版本,并且数据库服务器运行的是 DB2 version 8.2 或更新版本。
  • 在执行、准备执行或随后执行例程时,出现了一个无法处理的异常。这可能是由例程无法处理的逻辑编程错误引起的,也可能是由内部处理错误引起的。
  • 实现错误处理是一个好办法,捕捉每条 SQL 语句后的错误,这样,您可以用某种程度的粒度进行除错。

 

例程调用
通过执行 CALL 语句调用DB2 存储过程。CALL 语句在调用者和存储过程之间提供了参数检查和参数传递支持。通过在 SELECT 查询、subselect 查询、 VALUES 子句或 SQL 语句中支持表达式的地方(包括表函数中的 FROM 子句)引用 UDF,可以调用UDF。

为了成功调用存储过程或 UDF,对于例程,调用者必须有 EXECUTE 特权。

CLR 过程实例(C#)
下列实例使用了一个名为 EMPLOYEE 的表,该表包含在 SAMPLE 数据库中。

  • C# 外部代码文件
  • 实例 1:C# 参数样式 GENERAL 的过程
  • 实例 2:C# 参数样式 GENERAL WITH NULLS 的过程
  • 实例 3:返回结果集的 C# 过程

 

C# 外部代码文件结构
这些实例展示了各种各样的 C# 过程实现。每个实例包含两个部分: CREATE PROCEDURE 语句和该过程的外部 C# 代码实现,可以从该代码实现中构建相关的程序集。

包含了下列实例的过程实现的 C# 源代码文件名为 gwenProc.cs,其格式为:

C# 外部代码文件结构

            using System;
            using System.IO;
            using IBM.Data.DB2;
            namespace bizLogic
            {
            class empOps
            {          ...
            // C# procedures
            ...
            }
            }
            

如果该文件中的过程包含 SQL,则需要包括 IBM.Data.DB2 文件。该文件中有名称空间的声明和包含该过程的类 empOps。名称空间的使用是可选的。如果使用了名称空间,则该名称空间必须包含在 CREATE PROCEDURE 语句的 EXTERNAL 子句提供的程序集路径名中。

实例 1:C# 参数样式 GENERAL 的过程
该实例展示了下列内容:

  • 用于参数样式 GENERAL 的过程的 CREATE PROCEDURE 语句
  • 用于参数样式 GENERAL 的过程的 C# 代码

 

该过程接收雇员 ID 和当前奖金数额为输入。它检索该雇员的姓名和薪水。如果当前奖金数额为零,则基于该雇员的薪水计算新的奖金,并将之与该雇员的全名一起返回。如果未找到该雇员,则返回一个空字符串。

创建 C# 参数样式 GENERAL 的过程的代码

            CREATE PROCEDURE setEmpBonusGEN(IN empID CHAR(6), INOUT bonus Decimal(9,2),
            OUT empName VARCHAR(60))
            SPECIFIC SetEmpBonusGEN
            LANGUAGE CLR
            PARAMETER STYLE GENERAL
            DYNAMIC RESULT SETS 0
            PROGRAM TYPE SUB
            EXTERNAL NAME 'gwenProc.dll:bizLogic.empOps!SetEmpBonusGEN'
            EXECUTION CONTROL SAFE;
            

            public static void SetEmpBonusGEN(    String empID,
            ref Decimal bonus,
            out String empName)
            {
            // Declare local variables
            Decimal salary = 0;
            DB2Command myCommand = DB2Context.GetCommand();
            myCommand.CommandText =
            "SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY "
            + "FROM EMPLOYEE "
            + "WHERE EMPNO = '" + empID + '";
            DB2DataReader reader = myCommand.ExecuteReader();
            if (reader.Read())  // If employee record is found
            {
            // Get the employee's full name and salary
            empName = reader.GetString(0) + " " +
            reader.GetString(1) + ". " +
            reader.GetString(2);
            salary = reader.GetDecimal(3);
            if (bonus == 0)
            {
            if (salary > 75000)
            {
            bonus = salary * (Decimal)0.025;
            }
            else
            {
            bonus = salary * (Decimal)0.05;
            }
            }
            }
            else  // Employee not found
            {
            empName = "";  // Set output parameter
            }
            reader.Close();
            }
            

实例 2:C# 参数样式 GENERAL WITH NULLS 的过程
该实例展示了下列内容:

  • 用于参数样式 GENERAL WITH NULLS 的过程的 CREATE PROCEDURE 语句
  • 用于参数样式 GENERAL WITH NULLS 的过程的 C# 代码

 

该过程接收雇员 ID 和当前奖金数额为输入。如果输入参数不为空,则检索该雇员的姓名和薪水。如果当前奖金数额为零,则基于其薪水计算出新的奖金,并将之与该雇员的全名一起返回。如果未找到该雇员数据,则返回一个空字符串和整数。

创建 C# 参数样式 GENERAL WITH NULLS 的过程的代码

            CREATE PROCEDURE SetEmpbonusGENNULL(IN empID CHAR(6),
            INOUT bonus Decimal(9,2),
            OUT empName VARCHAR(60))
            SPECIFIC SetEmpbonusGENNULL
            LANGUAGE CLR
            PARAMETER STYLE GENERAL WITH NULLS
            DYNAMIC RESULT SETS 0
            FENCED
            PROGRAM TYPE SUB
            EXTERNAL NAME 'gwenProc.dll:bizLogic.empOps!SetEmpBonusGENNULL'
            EXECUTION CONTROL SAFE;
            

            public static void SetEmpBonusGENNULL(    String empID,
            ref Decimal bonus,
            out String empName,
            Int16[] NullInds)
            {
            Decimal salary = 0;
            if (NullInds[0] == -1) // Check if the input is null
            {
            NullInds[1] = -1;    // Return a NULL bonus value
            empName = "";        // Set output value
            NullInds[2] = -1;    // Return a NULL empName value
            }
            else
            {
            DB2Command myCommand = DB2Context.GetCommand();
            myCommand.CommandText =
            "SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY "
            + "FROM EMPLOYEE "
            + "WHERE EMPNO = '" + empID + "'";
            DB2DataReader reader = myCommand.ExecuteReader();
            if (reader.Read())  // If employee record is found
            {
            // Get the employee's full name and salary
            empName = reader.GetString(0) + " "
            +
            reader.GetString(1) + ". " +
            reader.GetString(2);
            salary = reader.GetDecimal(3);
            if (bonus == 0)
            {
            if (salary > 5000)
            {
            bonus = salary * (Decimal)0.025;
            NullInds[1] = 0; // Return a non-NULL value
            }
            else
            {
            bonus = salary * (Decimal)0.05;
            NullInds[1] = 0; // Return a non-NULL value
            }
            }
            }
            else  // Employee not found
            {
            empName = "*sdq;;     // Set output parameter
            NullInds[2] = -1;     // Return a NULL value
            }
            reader.Close();
            }
            }
            

实例 3:返回结果集的 C# 参数样式 GENERAL 的过程
该实例展示了下列内容:

  • 用于返回结果集的外部 C# 过程的 CREATE PROCEDURE 语句
  • 用于返回结果集的参数样式 GENERAL 的过程的 C# 代码

 

该过程接收表名为参数。它返回一个结果集,其中包含输入参数指定的表中的所有行。这是在过程返回时,通过使给定查询集的 DB2DataReader 处于打开状态来完成的。具体地说,如果未执行 reader.Close(),则返回结果集。

创建返回结果集的 C# 过程的代码

            CREATE PROCEDURE ReturnResultSet(IN tableName
            VARCHAR(20))
            SPECIFIC ReturnResultSet
            DYNAMIC RESULT SETS 1
            LANGUAGE CLR
            PARAMETER STYLE GENERAL
            FENCED
            PROGRAM TYPE SUB
            EXTERNAL NAME
            'gwenProc.dll:bizLogic.empOps!ReturnResultSet'
            EXECUTION CONTROL SAFE  ;
            

            public static void ReturnResultSet(string tableName)
            {
            DB2Command myCommand = DB2Context.GetCommand();
            // Set the SQL statement to be executed and execute it.
            myCommand.CommandText = "SELECT * FROM " + tableName;
            DB2DataReader reader = myCommand.ExecuteReader();
            // The DB2DataReader contains the result of the query.
            // This result set can be returned with the procedure,
            // by simply NOT closing the DB2DataReader.
            // Specifically, do NOT execute reader.Close();
            }
            

CLR UDF 实例(C#)
下列实例使用了一个名为 EMPLOYEE 的表,该表包含在 SAMPLE 数据库中。 SAMPLE 数据库是一个数据库定义,可用它来创建示例。使用 db2sampl 命令创建该数据库。在创建您自己的 C# CLR UDF 时,可参考这些实例:

  • C# 外部代码文件
  • 实例 1:C# 参数样式 SQL 的表函数
  • 实例 2:C# 参数样式 SQL 的标量函数

 

C# 外部代码文件
下列实例展示了各种 C# UDF 实现。每个 UDF 都提供了 CREATE FUNCTION 语句和相应的 C# 源代码,可以从中构建相关的程序集。包含在下列实例中使用的函数声明的 C# 源文件的名称为 gwenUDF.cs,其格式如下所示:

C# 外部代码文件格式

            using System;
            using System.IO;
            using IBM.Data.DB2;
            namespace bizLogic
            {
            ...
            // Class definitions that contain UDF declarations
            // and any supporting class definitions
            ...
            }
            

函数声明必须包含在 C# 文件的一个类中。名称空间的使用是可选的。如果使用了名称空间,则该名称空间必须包含在 CREATE PROCEDURE 语句的 EXTERNAL 子句提供的程序集路径名中。如果该函数包含 SQL,则需要包含 IBM.Data.DB2。

实例 1:C# 参数样式 SQL 的表函数
该实例展示了下列内容:

  • 用于参数样式 SQL 的表函数的 CREATE FUNCTION 语句
  • 用于参数样式 SQL 的表函数的 C# 代码

 

该表函数返回一个包含了雇员数据行的表,这些雇员数据是从数据数组创建的。该实例有两个相关的类。类 person 表示雇员,类 empOps 包含使用类 person 的例程表 UDF。雇员的薪水信息是基于输入参数的值来更新的。该实例中的数据数组是在首次调用表函数时,在表函数自身中创建的。还可以通过从文件系统中的文本文件读取数据来创建这样的数组。应该将数组数据值写入高速暂存,这样就可以在以后调用该表函数时访问这些数据。

每次调用表函数时,都只从数组读取一条记录,并在该函数返回的表中生成一行。通过将表函数的输出参数设置为想要的行值来生成表中的行。最后一次调用表函数之后,返回由生成行组成的表。

创建 C# 参数样式 SQL 的表函数的代码

            CREATE FUNCTION tableUDF(double)
            RETURNS TABLE (name varchar(20),
            job varchar(20),
            salary double)
            EXTERNAL NAME 'gwenUDF.dll:bizLogic.empOps!tableUDF'
            LANGUAGE CLR
            PARAMETER STYLE SQL
            NOT DETERMINISTIC
            FENCED
            SCRATCHPAD 10
            FINAL CALL
            DISALLOW PARALLEL
            NO DBINFO
            EXECUTION CONTROL SAFE  ;
            

            // The class Person is a supporting class for
            // the table function UDF, tableUDF, below.
            class Person
            {
            private String name;
            private String position;
            private Int32 salary;
            public Person(String newName, String newPosition, Int32
            newSalary)
            {
            this.name = newName;
            this.position = newPosition;
            this.salary = newSalary;
            }
            public String getName()
            {
            return this.name;
            }
            public String getPosition()
            {
            return this.position;
            }
            public Int32 getSalary()
            {
            return this.salary;
            }
            }
            

            class empOps
            {
            {
            public static void TableUDF( Double factor, out String name,
            out String position, out Double salary,
            Int16 factorNullInd, out Int16 nameNullInd,
            out Int16 positionNullInd, out Int16 salaryNullInd,
            ref String sqlState, String funcName,
            String specName, ref String sqlMessageText,
            Byte[] scratchPad, Int32 callType)
            {
            Int16 intRow = 0;
            // Create an array of Person type information
            Person[] Staff = new
            Person[3];
            Staff[0] = new Person("Gwen", "Developer", 10000);
            Staff[1] = new Person("Andrew", "Developer", 20000);
            Staff[2] = new Person("Liu", "Team Leader", 30000);
            salary = 0;
            name = position = "";
            nameNullInd = positionNullInd = salaryNullInd = -1;
            switch(callType)
            {
            case (-2):  // Case SQLUDF_TF_FIRST:
            break;
            case (-1):  // Case SQLUDF_TF_OPEN:
            intRow = 1;
            scratchPad[0] = (Byte)intRow;  // Write to scratchpad
            break;
            case (0):   // Case SQLUDF_TF_FETCH:
            intRow = (Int16)scratchPad[0];
            if (intRow > Staff.Length)
            {
            sqlState = "02000";  // Return an error SQLSTATE
            }
            else
            {
            // Generate a row in the output table
            // based on the Staff array data.
            name =
            Staff[intRow-1].getName();
            position = Staff[intRow-1].getPosition();
            salary = (Staff[intRow-1].getSalary[]] * factor;
            nameNullInd = 0;
            positionNullInd = 0;
            salaryNullInd = 0;
            }
            intRow++;
            scratchPad[0] = (Byte)intRow;  // Write scratchpad
            break;
            case (1):   // Case SQLUDF_TF_CLOSE:
            break;
            case (2):   // Case SQLUDF_TF_FINAL:
            break;
            }
            }
            }
            

实例 2:C# 参数样式 SQL 的标量函数
该实例展示了下列内容:

  • 用于参数样式 SQL 的标量函数的 CREATE FUNCTION 语句
  • 用于参数样式 SQL 的标量函数的 C# 代码

 

该标量函数为它所操作的每个输入值返回单个计数值。对于输入值集合中的第 n 个输入值,其输出标量值就为值 n。每次调用标量函数时,若该调用与行或值的输入集中的每一行或每个值有关联,则将计数增加 1,并返回当前计数值。然后,在高速暂存存储器缓冲区保存该计数,用以在标量函数的每次调用之间维护该计数值。

有些情况下,可以很容易地调用标量函数,例如,我们具有下列表定义:


            CREATE TABLE T (i1 INTEGER);
            INSERT INTO T VALUES 12, 45, 16, 99;
            

像下面这样的简单查询就可用于调用该标量函数:


            SELECT countUp(i1) as count, i1 FROM T;
            

该查询的输出为:


            COUNT           I1
            -----------     ----------
            1               12
            2               45
            3               16
            4               99
            

这个标量 UDF 极其简单。除了只返回行的计数,还可以使用标量函数格式化现有列中的数据。例如,您可以给地址列中的每个值追加一个字符串,也可以通过一系列输入字符串构建一个复杂字符串,还可以在必须存储中间结果的数据集上进行复杂的数学计算。

创建 C# 参数样式 SQL 的标量函数的代码

            CREATE FUNCTION countUp(INTEGER)
            RETURNS INTEGER
            LANGUAGE CLR
            PARAMETER STYLE SQL
            FENCED
            SCRATCHPAD 10
            FINAL CALL
            VARIANT
            NO SQL
            EXTERNAL NAME 'gwenUDF.dll:bizLogic.empOps!CountUp'
            EXECUTION CONTROL SAFE ;
            

            class empOps
            {
            public static void CountUp(     Int32 input,
            out Int32 outCounter,
            Int16 inputNullInd,
            out Int16 outCounterNullInd,
            ref String sqlState,
            String funcName,
            String specName,
            ref String sqlMessageText,
            Byte[] scratchPad,
            Int32 callType)
            {
            Int32 counter = 1;        switch(callType)
            {
            case -1: // case SQLUDF_FIRST_CALL
            scratchPad[0] = (Byte)counter;
            outCounter = counter;
            outCounterNullInd = 0;
            break;
            case 0:  // case SQLUDF_NORMAL_CALL:
            counter = (Int32)scratchPad[0];
            counter = counter + 1;
            outCounter = counter;
            outCounterNullInd = 0;
            scratchPad[0] =
            (Byte)counter;
            break;
            case 1:  // case SQLUDF_FINAL_CALL:
            counter =
            (Int32)scratchPad[0];
            outCounter = counter;
            outCounterNullInd = 0;
            break;
            default: // Should never enter here
            // * Required so that at compile time
            //   out parameter outCounter is always set *
            outCounter = (Int32)(0);
            outCounterNullInd = -1;
            sqlState="ABCDE";
            sqlMessageText = "Should not get here: Default
            case!";
            break;
            }
            }
            }
            

CLR 过程实例(VB)
在创建自己的 Visual Basic CLR 过程时,可以参考下列实例:

  • Visual Basic 外部代码文件
  • 实例 1:Visual Basic 参数样式 GENERAL 的过程
  • 实例 2:Visual Basic 参数样式 SQL 的过程
  • 实例 3: PROGRAM TYPE MAIN 样式中的 Visual Basic 过程

 

Visual Basic 外部代码文件
这些实例展示了各种 Visual Basic 过程实现。每个实例都包含两个部分:CREATE PROCEDURE 语句和该过程的外部 Visual Basic 代码实现,可以从中构建相关的程序集。

下列实例中包含过程实现的 Visual Basic 源代码文件名为 gwenVbProc.vb,其格式如下:

Visual Basic 外部代码文件格式

            using System;
            using System.IO;
            using IBM.Data.DB2;
            Namespace bizLogic
            Class empOps
            ...
            ' Visual Basic procedures
            ...
            End Class
            End Namespace
            

文件顶部指示了该文件的包含内容(inclusion)。如果该文件中的过程包含 SQL,则需要包含 IBM.Data.DB2。该文件中有名称空间的声明和包含该过程的类 empOps。名称空间的使用是可选的。如果使用了名称空间,则该名称空间必须包含在 CREATE PROCEDURE 语句的 EXTERNAL 子句提供的程序集路径名中。

值得注意的是该文件的名称、名称空间和包含给定过程实现的类名。这些名称十分重要,因为每个过程的 CREATE PROCEDURE 的 EXTERNAL 子句都必须指定这些信息,以便 DB2 能够找到程序集和 CLR 过程的类。

实例 1:Visual Basic 参数样式 GENERAL 的过程
该实例展示了下列内容:

  • 用于参数样式 GENERAL 的过程的 CREATE PROCEDURE 语句
  • 用于参数样式 GENERAL 的过程的 Visual Basic 代码

 

该过程接收雇员 ID 和当前奖金数额为输入。它检索该雇员的姓名和薪水。如果当前奖金数额为零,则基于该雇员的薪水计算新的奖金,并将之与该雇员的全名一起返回。如果未找到该雇员,则返回一个空字符串。

创建 Visual Basic 参数样式 GENERAL 的过程的代码

            CREATE PROCEDURE SetEmpBonusGEN(IN empId CHAR(6),
            INOUT bonus Decimal(9,2),
            OUT empName VARCHAR(60))
            SPECIFIC setEmpBonusGEN
            LANGUAGE CLR
            PARAMETER STYLE GENERAL
            DYNAMIC RESULT SETS 0
            FENCED
            PROGRAM TYPE SUB
            EXTERNAL NAME 'gwenVbProc.dll:bizLogic.empOps!SetEmpBonusGEN'
            EXECUTION CONTROL SAFE ;
            

            Public Shared Sub SetEmpBonusGEN(ByVal empId As String, _
            ByRef bonus As Decimal, _
            ByRef empName As String)
            Dim salary As Decimal
            Dim myCommand As DB2Command
            Dim myReader As DB2DataReader
            salary = 0
            myCommand = DB2Context.GetCommand()
            myCommand.CommandText = _
            "SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY " _
            + "FROM EMPLOYEE " _
            + "WHERE EMPNO = '" + empId + "'"
            myReader = myCommand.ExecuteReader()
            If myReader.Read()  ' If employee record is found
            ' Get the employee's full name and salary
            empName = myReader.GetString(0) + " " _
            + myReader.GetString(1) + ". " _
            + myReader.GetString(2)
            salary = myReader.GetDecimal(3)
            If bonus = 0
            If salary > 5000
            bonus = salary * 0.025
            Else
            bonus = salary * 0.05
            End If
            End If
            Else  ' Employee not found
            empName = ""  ' Set output parameter
            End If
            myReader.Close()
            End Sub
            

实例 2:Visual Basic 参数样式 SQL 的过程
该实例展示了下列内容:

  • 用于参数样式 SQL 的过程的 CREATE PROCEDURE 语句
  • 用于参数样式 SQL 的过程的 Visual Basic 代码

 

该过程接收雇员 ID 和当前奖金数额为输入。它检索该雇员的姓名和薪水。如果当前奖金数额为零,则基于该雇员的薪水计算新的奖金,并将之与该雇员的全名一起返回。如果未找到该雇员,则返回一个空字符串。

用参数在参数样式 SQL 中创建 Visual Basic 过程的代码

            CREATE PROCEDURE SetEmpBonusSQL(IN empId CHAR(6),
            INOUT bonus Decimal(9,2),
            OUT empName VARCHAR(60))
            SPECIFIC SetEmpBonusSQL
            LANGUAGE CLR
            PARAMETER STYLE SQL
            DYNAMIC RESULT SETS 0
            FENCED
            PROGRAM TYPE SUB
            EXTERNAL NAME 'gwenVbProc.dll:bizLogic.empOps!SetEmpBonusSQL'
            EXECUTION CONTROL SAFE ;
            

            Public Shared Sub SetEmpBonusSQL(byVal empId As String, _
            byRef bonus As Decimal, _
            byRef empName As String, _
            byVal empIdNullInd As Int16, _
            byRef bonusNullInd As Int16, _
            byRef empNameNullInd As Int16, _
            byRef sqlState As String, _
            byVal funcName As String, _
            byVal specName As String, _
            byRef sqlMessageText As String)
            ' Declare local host variables
            Dim salary As Decimal
            Dim myCommand As DB2Command
            Dim myReader As DB2DataReader
            salary = 0
            If empIdNullInd = -1   ' Check if the input is null
            bonusNullInd = -1   ' Return a NULL Bonus value
            empName = ""
            empNameNullInd = -1 ' Return a NULL empName value
            Else
            myCommand = DB2Context.GetCommand()
            myCommand.CommandText = _
            "SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY " _
            + "FROM EMPLOYEE " _
            + " WHERE EMPNO = '" + empId + "'"
            myReader = myCommand.ExecuteReader()
            If myReader.Read()  ' If employee record is found
            '  Get the employee's full name and salary
            empName = myReader.GetString(0) + " "
            + myReader.GetString(1) _
            + ". " +  myReader.GetString(2)
            empNameNullInd = 0
            salary = myReader.GetDecimal(3)
            If bonus = 0
            If salary > 5000
            bonus = salary * 0.025
            bonusNullInd = 0  ' Return a non-NULL value
            Else
            bonus = salary * 0.05
            bonusNullInd = 0  ' Return a non-NULL value
            End If
            End If
            Else  ' Employee not found
            empName = ""            ' Set output parameter
            empNameNullInd = -1     ' Return a NULL value
            End If
            myReader.Close()
            End If
            End Sub
            

实例 3:具有 PROGRAM TYPE MAIN 样式的 Visual Basic 过程
该实例展示了下列内容:

  • 用于过程的 CREATE PROCEDURE 语句,该过程使用主程序(main program)样式
  • 使用 MAIN 程序样式中的 Visual Basic 参数样式 GENERAL WITH NULLS 的代码

 

如果在主程序样式中实现例程,则必须在 CREATE PROCEDURE 语句中使用值 MAIN 指定 PROGRAM TYPE 子句。参数是在 CREATE PROCEDURE 语句中指定的,但在代码实现中,需要在一个 argc 整型参数和一个 argv 参数数组中将参数传递给例程。

在程序类型 MAIN 样式中创建 Visual Basic 过程的代码

            CREATE PROCEDURE MainStyle(IN empId CHAR(6),
            INOUT bonus Decimal(9,2),
            OUT empName VARCHAR(60))
            SPECIFIC mainStyle
            DYNAMIC RESULT SETS 0
            LANGUAGE CLR
            PARAMETER STYLE GENERAL WITH NULLS
            FENCED
            PROGRAM TYPE MAIN
            EXTERNAL NAME 'gwenVbProc.dll:bizLogic.empOps!Main'
            EXECUTION CONTROL SAFE ;
            

            Public Shared Sub Main( byVal argc As Int32, _
            byVal argv As Object())
            Dim myCommand As DB2Command
            Dim myReader As DB2DataReader
            Dim empId As String
            Dim bonus As Decimal
            Dim salary As Decimal
            Dim nullInds As Int16()
            empId = argv(0)  ' argv[0] (IN)    nullInd = argv[3]
            bonus = argv(1)  ' argv[1] (INOUT) nullInd = argv[4]
            ' argv[2] (OUT)   nullInd = argv[5]
            salary = 0
            nullInds = argv(3)
            If nullInds(0) = -1     ' Check if the empId input is null
            nullInds(1) = -1     ' Return a NULL Bonus value
            argv(1) = ""         ' Set output parameter empName
            nullInds(2) = -1     ' Return a NULL empName value
            Return
            Else
            ' If the employee exists and the current bonus is 0,
            ' calculate a new employee bonus based on the employee's
            ' salary.  Return the employee name and the new bonus
            myCommand = DB2Context.GetCommand()
            myCommand.CommandText = _
            "SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY " _
            + " FROM EMPLOYEE " _
            + " WHERE EMPNO = '" + empId + "'"
            myReader = myCommand.ExecuteReader()
            If myReader.Read()  ' If employee record is found
            ' Get the employee's full name and salary
            argv(2) = myReader.GetString(0) + " "  _
            + myReader.GetString(1) + ". " _
            + myReader.GetString(2)
            nullInds(2) = 0
            salary = myReader.GetDecimal(3)
            If bonus = 0
            If salary > 5000
            argv(1) = salary * 0.025
            nullInds(1) = 0  ' Return a non-NULL value
            Else
            argv(1) = Salary * 0.05
            nullInds(1) = 0  ' Return a non-NULL value
            End If
            End If
            Else  ' Employee not found
            argv(2) = ""      ' Set output parameter
            nullInds(2) = -1  ' Return a NULL value
            End If
            myReader.Close()
            End If
            End Sub
            

CLR UDF 实例(VB)
在创建您自己的 Visual Basic CLR UDF 时,可以参考下列实例:

  • Visual Basic 外部代码文件
  • 实例 1:Visual Basic 参数样式 SQL 的表函数
  • 实例 2:Visual Basic 参数样式 SQL 的标量函数

 

Visual Basic 外部代码文件
下列实例展示了各种 Visual Basic UDF 实现。每个 UDF 都提供了 CREATE FUNCTION 语句,以及相应的 Visual Basic 源代码,您可以从中构建相关的程序集。包含了在下列实例中使用的函数声明的 Visual Basic 源文件的名称为 gwenVbUDF.cs,其格式如下所示:

Visual Basic 外部代码文件格式

            using System;
            using System.IO;
            using IBM.Data.DB2;
            Namespace bizLogic
            ...
            ' Class definitions that contain UDF declarations
            ' and any supporting class definitions
            ...
            End Namespace
            

该函数声明必须包含在 Visual Basic 文件的一个类中。名称空间的使用是可选的。如果使用了名称空间,则名称空间必须包含在 CREATE PROCEDURE 语句的 EXTERNAL 子句提供的程序集路径名中。如果该函数包含 SQL,则需要包含 IBM.Data.DB2。

实例 1:Visual Basic 参数样式 SQL 的表函数
该实例展示了下列内容:

  • 用于参数样式 SQL 的表函数的 CREATE FUNCTION 语句
  • 用于参数样式 SQL 的表函数的 Visual Basic 代码

 

该表函数返回一个包含了雇员数据行的表,这些雇员数据是从数据数组创建的。该实例有两个相关的类。类 person 表示雇员,类 empOps 包含使用类 person 的例程表 UDF。雇员的薪水信息是基于输入参数的值来更新的。该实例中的数据数组是在首次调用表函数时,在表函数自身中创建的。还可以通过从文件系统中的文本文件读取数据来创建这样的数组。应该将数组数据值写入高速暂存,这样就可以在以后调用该表函数时访问这些数据。

每次调用表函数时,都从数组读取一条记录,并在该函数返回的表中生成一个行。通过将表函数的输出参数设置为想要的行值来生成表中的行。最后一次调用表函数之后,返回由生成行所组成的表。

创建 Visual Basic 参数样式 SQL 的表函数的代码

            CREATE FUNCTION TableUDF(double)
            RETURNS TABLE (name varchar(20),
            job varchar(20),
            salary double)
            EXTERNAL NAME 'gwenVbUDF.dll:bizLogic.empOps!TableUDF'
            LANGUAGE CLR
            PARAMETER STYLE SQL
            NOT DETERMINISTIC
            FENCED
            SCRATCHPAD 10
            FINAL CALL
            DISALLOW PARALLEL
            NO DBINFO
            EXECUTION CONTROL SAFE
            

            Class Person
            ' The class Person is a supporting class for
            ' the table function UDF, tableUDF, below.
            Private name As String
            Private position As String
            Private salary As Int32
            Public Sub New(ByVal newName As String, _
            ByVal newPosition As String, _
            ByVal newSalary As Int32)
            name = newName
            position = newPosition
            salary = newSalary
            End Sub
            Public Property GetName() As String
            Get
            Return name
            End Get
            Set (ByVal value As String)
            name = value
            End Set
            End Property
            Public Property GetPosition() As String
            Get
            Return position
            End Get
            Set (ByVal value As String)
            position = value
            End Set
            End Property
            Public Property GetSalary() As Int32
            Get
            Return salary
            End Get
            Set (ByVal value As Int32)
            salary = value
            End Set
            End Property
            End Class
            

            Class empOps
            Public Shared Sub TableUDF(byVal factor as Double, _
            byRef name As String, _
            byRef position As String, _
            byRef salary As Double, _
            byVal factorNullInd As Int16, _
            byRef nameNullInd As Int16, _
            byRef positionNullInd As Int16, _
            byRef salaryNullInd As Int16, _
            byRef sqlState As String, _
            byVal funcName As String, _
            byVal specName As String, _
            byRef sqlMessageText As String, _
            byVal scratchPad As Byte(), _
            byVal callType As Int32)
            Dim intRow As Int16
            intRow = 0
            ' Create an array of Person type information
            Dim staff(2) As Person
            staff(0) = New Person("Gwen", "Developer", 10000)
            staff(1) = New Person("Andrew", "Developer", 20000)
            staff(2) = New Person("Liu", "Team Leader", 30000)
            ' Initialize output parameter values and NULL indicators
            salary = 0
            name = position = ""
            nameNullInd = positionNullInd = salaryNullInd = -1
            Select callType
            Case -2   ' Case SQLUDF_TF_FIRST:
            Case -1   ' Case SQLUDF_TF_OPEN:
            intRow = 1
            scratchPad(0) = intRow  ' Write to scratchpad
            Case 0    ' Case SQLUDF_TF_FETCH:
            intRow = scratchPad(0)
            If intRow > staff.Length
            sqlState = "02000"  ' Return an error SQLSTATE
            Else
            ' Generate a row in the output table
            ' based on the staff array data.
            name = staff(intRow).GetName()
            position = staff(intRow).GetPosition()
            salary = (staff(intRow).GetSalary()) * factor
            nameNullInd = 0
            positionNullInd = 0
            salaryNullInd = 0
            End If
            intRow = intRow + 1
            scratchPad(0) = intRow  ' Write scratchpad
            Case 1    ' Case SQLUDF_TF_CLOSE:
            Case 2    ' Case SQLUDF_TF_FINAL:
            End Select
            End Sub
            End Class
            

实例 2:Visual Basic 参数样式 SQL 的标量函数
该实例展示了下列内容:

  • 用于参数样式 SQL 的标量函数的 CREATE FUNCTION 语句
  • 用于参数样式 SQL 的标量函数的 Visual Basic 代码

 

该标量函数为它操作的每个输入值返回单个计数值。对于输入值集合中的第 n 个输入值,其输出标量值就为值 n。每次调用标量函数时,若该调用与行或值的输入集中的每一行或每个值有关联,则将计数增加 1,并返回当前计数值。然后,在高速暂存存储器缓冲区保存该计数,以便在标量函数的每次调用之间维护该计数值。

可以很容易地调用该标量函数,例如,我们按以下方式定义一个表:


            CREATE TABLE T (i1 INTEGER);
            INSERT INTO T VALUES 12, 45, 16, 99;
            

像下面这样的简单查询就可用于调用该标量函数:


            SELECT my_count(i1) as count, i1 FROM T;
            

该查询的输出为:


            COUNT           I1
            -----------     ----------
            1               12
            2               45
            3               16
            4               99
            

这个标量 UDF 极其简单。除了只返回行的计数,您还可以使用标量函数格式化现有列中的数据。例如,您可以给地址列中的每个值追加一个字符串,也可以通过一系列输入字符串构建一个复杂字符串,还可以在必须存储中间结果的数据集上进行复杂的数学计算。

创建 Visual Basic 参数样式 SQL 的标量函数的代码

            CREATE FUNCTION mycount(INTEGER)
            RETURNS INTEGER
            LANGUAGE CLR
            PARAMETER STYLE SQL
            FENCED
            SCRATCHPAD 10
            FINAL CALL
            VARIANT
            NO SQL
            EXTERNAL NAME 'gwenUDF.dll:bizLogic.empOps!CountUp'
            EXECUTION CONTROL SAFE;
            

            Class empOps
            Public Shared Sub CountUp(byVal input As Int32, _
            byRef outCounter As Int32, _
            byVal nullIndInput As Int16, _
            byRef nullIndOutCounter As Int16, _
            byRef sqlState As String, _
            byVal qualName As String, _
            byVal specName As String, _
            byRef sqlMessageText As String, _
            byVal scratchPad As Byte(), _
            byVal callType As Int32)
            Dim counter As Int32
            counter = 1
            Select callType
            case -1           ' case SQLUDF_TF_OPEN_CALL
            scratchPad(0) = counter
            outCounter = counter
            nullIndOutCounter = 0
            case 0              'case SQLUDF_TF_FETCH_CALL:
            counter = scratchPad(0)
            counter = counter + 1
            outCounter = counter
            nullIndOutCounter = 0
            scratchPad(0) = counter
            case 1             'case SQLUDF_CLOSE_CALL:
            counter = scratchPad(0)
            outCounter = counter
            nullIndOutCounter = 0
            case Else          ' Should never enter here
            ' These cases won't occur for the following reasons:
            ' Case -2  (SQLUDF_TF_FIRST)     ->No FINAL CALL in CREATE stmt
            ' Case 2   (SQLUDF_TF_FINAL)     ->No FINAL CALL in CREATE stmt
            ' Case 255 (SQLUDF_TF_FINAL_CRA) ->No SQL used in the function
            '
            ' * Note!*
            ' ---------
            ' The Else is required so that at compile time
            ' out parameter outCounter is always set *
            outCounter = 0
            nullIndOutCounter = -1
            End Select
            End Sub
            End Class
            

结束语
但愿本文有助于您实现 DB2 .NET CLR 例程。有关更多令人激动的 DB2 .NET 数据提供者的信息,请查看 DB2 Stinger 中附带的大量实例和完整的应用程序示例。

 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 myfaq.com.cn All rights reserved. www.myfaq.com.cn 版权所有