c#

位置:IT落伍者 >> c# >> 浏览文章

Visual Stuido.NET扩展存储过程


发布日期:2021年03月15日
 
Visual Stuido.NET扩展存储过程

建立数据库框架

用Sql Sever新建一个数据库Railway并在Railway中加入两张表

Routes

( TrainID(int) AllStations(varchar())

)

Trains

(

TrainID(int)

TrainName(varchar())

)

下划线代表主键Trains表中两项一起作主键是为了列车的别名例如T=特快=特等等

最后在Route表中加入如下记录

( |北京小时|石家庄小时|郑州小时|武昌小时|广州小时|)

( |哈尔滨小时|北京小时|石家庄小时|郑州 小时|西安 小时|成都 小时|)

在Trains表中加入:

( J)

( 极快)

( J)

( 极快)

Route表中第二项的格式是|起点站小时|下一站离上一站的时间||终点站离上一站的时间|

提出问题

寻找北京到郑州的最快路线

寻找哈尔滨到广州的最快路线

第一个问题很容易看穿因为J和J都经过北京和郑州只要比较这两条线路谁更快即可结果应该是J胜出小时此问题不用扩展存储过程也很容易解决

第二个问题相对就复杂点肯定需要中转但应该选择哪个作为中转站?北京石家庄还是郑州?这个问题的算法虽然不难但在普通存储过程里却很难编写

用扩展存储过程解决第一个问题

在VsNet中建立一个扩展存储过程项目命名为xsTrainQuery点确定之后在弹出窗口中设置扩展存储过程名为xs_TrainQuery完成

现在VsNet自动生成了几个文件如果你是我这种新手就先读读Readmetxt里面对每个文件的作用和加入运行删除扩展存储过程xs_TrainQuery的方法有详细解释

让我们把注意力集中到proccpp这个文件的作用是实现xsTrainQuerycpp中定义的接口其内容如下

#include

// 一些全局常量声明

#ifdef __cplusplus

extern C {

#endif

RETCODE __declspec(dllexport) xs_TrainQuery(SRV_PROC *srvproc); //声明

#ifdef __cplusplus

}

RETCODE __declspec(dllexport) xs_TrainQuery(SRV_PROC *srvproc) //实现

{

//

}

其中RETCODE xs_TrainQuery(SRV_PROC*)正是在数据库中运行扩展存储过程xs_TrainQuery后将运行的方法所以只需要在这一部分中加入适当的代码就做出了一个扩展存储过程

SRV_PROC是一个结构在中定义代表一个数据库客户端连接的句柄srvh中几乎所有的方法都要用到这个句柄但这个句柄背后究竟有什么数据我们完全不用关心(忽然想起写硕士毕业论文时觉得CBitmap类不好用所以自己傻乎乎地超级郁闷地做一个Image类的时候……)

此扩展存储过程调用的格式应该是xs_TrainQuery [起点站名终点站名结果 Output]那么解决第一个问题将分为以下几个步骤

传入两个车站名的值

读入同时含此二车站的所有列车路线

寻找最短的路线

生成结果并传出之

第一步让我们解决传值问题

扩展存储过程中的值传入严格来说包括先获得参数的类型长度等信息再设置此参数对应的局部变量的类型和长度最后读入此参数即先调用srv_paraminfo再alloc一个合适的内存空间最后调用srv_paraminfo将入参值存入此内存空间

srv_paraminfo的格式为

int srv_paraminfo(

SRV_PROC *srvproc

int n

BYTE *pbType

ULONG *pcbMaxLen

ULONG *pcbActualLen

BYTE *pbData

BOOL *pfNull )

其中

n代表是第几个参数比如xs_TrainQuery 北京 成都 成都的n值为

pbType pcbMaxLen pcbActualLen分别代表指向此参数的类型最大长度和实际传入长度的指针

pbData代表指向此参数值的指针

pfNull代表此参数是否为NULL若是则运行srv_paraminfo后*pfNull将被设置为TRUE当此参数的信息存在时srv_paraminfo返回SUCCEED否则返回FAIL

我们现在要读入起点和终点站名就需要如下代码

{

BOOL bfNull; // 记录入参是否为空(NULL)

PBYTE pbType; // 入参的类型

ULONG ulMaxLen = ; // 入参的最大长度令为字节

ULONG ulActualLen; // 入参的实际长度

PBYTE pbStart; // 起点站名字

PBYTE pbEnd; // 终点站名字

// 获得第个参数即起点站名的类型长度等信息

if ( srv_paraminfo(srvproc pbType &ulMaxLen &ulActualLen NULL &bfNULL) !=

SUCCEED )

{

// 一些异常处理代码

}

// 为起点站入参分配空间

pbStart = (PBYTE)::malloc(ulActualLen);

if ( pbStart == NULL)

{

// 一些异常处理代码

}

// 获得第个参数的值

if ( srv_paraminfo(srvproc pbType &ulMaxLen &ulActualLen pbStart &bfNULL)

!= SUCCEED )

{

// 一些异常处理代码

}

// 重复上面的三步但n变为pbStart变为pbEnd读入终点站名

}

第二步从数据库中取得含二车站名的所有路线

在扩展存储过程中连接数据库有两种方法第一种请参见MS Sql Server ODS示例中的xp_dblib这里只介绍第二种方法采用SQL开头的一系列ODBC API

为了使用这些API首先要在proccpp或者stdafxh中加入#include

一次数据库连接的过程包括以下几个步骤

初始化ODBC连接并分配环境句柄

设置环境

根据环境句柄分配数据库连接句柄

调用SQLDriverConnect连接MS SQL Serve数据源

分配和使用语句

调用SQLDisconnect断开连接

依次释放分配的句柄

直接用例子来说明现在我们要从数据库中读取含此二车站名的所有路线可以用下面的代码

{

SQLHENV hEnv = SQL_NULL_ENV; // 环境句柄

SQLHDBC hDbc = SQL_NULL_HDBC; // 连接句柄

SQLHSTMT hStmt = SQL_NULL_HSTMT; // 语句句柄

SQLCHAR connStr[] = Driver={SQL Server};SERVER=localhost;UID=你的用户名;PWD=你的密码;DATABASE=Railway;;

// 分配环境句柄

SQLAllocHandle(SQL_HANDLE_ENV NULL &hEnv);

// 设置连接环境ODBC版本设为x

SQLSetEnvAttr(hEnv SQL_ATTR_ODBC_VERSION (SQLPOINTER)SQL_OV_ODBC SQL_IS_INTEGER);

// 根据环境分配连接句柄

SQLAllocHandle(SQL_HANDLE_DBC hEnv &hDbc);

// 建立数据库连接

SQLDriverConnect(hDbc NULL connStr SQL_NTS NULL NULL SQL_DRIVER_NOPROMPT);

// 根据连接分配语句句柄

SQLAllocHandle(SQL_HANDLE_STMT hDbc &hStmt);

// 定义查询语句query别忘记#include

TCHAR query[] = select RoutesTrainID AllStations from Routes Trains where RoutesTrainID=TrainsTrainID and AllStations like %;

_tcscat(query (TCHAR*)pbStart);

_tcscat(query %);

_tcscat(query (TCHAR*)pbEnd);

_tcscat(query %);

// 现在query=select Routes…… like %起点站%终点站%我们运行此查询

SQLExecDirect(hStmt (SQLCHAR*)query SQL_NTS);

// 获得查询所得的行数

UINT uiRowsCount; //结果集中的行数

SQLSetStmtAttr(hStmt SQL_ATTR_ROWS_FETCHED_PTR) (void*)&uiRowsCount sizeof(SQLINTEGER));

// 初始化结果集数组包括两个TrainID和AllStations

int *piTrainID = (int*)::malloc(uiRowsCount * sizeof(int));

PCHAR *ppcStations = (PCHAR*)::malloc(uiRowsCount * sizeof(PCHAR));

// 因为结果集中第一列即TrainID为整型长度不变故可以直接绑定

SQLBindCol(hStmt SQL_INTEGER (SQLPOINTER)piTrainID sizeof(int) NULL);

// 取出第二列的数据因为其长度不定故必须先获得长度

SQLINTEGER lColLen; // 字符串的列即AllStations长度

UINT nRow = ; // 行标

while (SQLFetch(hStmt) == SQL_SUCCESS_WITH_INFO) //取出一行

{

// 获得长度

SQLGetData(hStmt SQL_CHAR NULL &lColLen);

// 根据长度为第二列的数据分配空间

ppcStation[nRow] = ::malloc(lColLen);

// 获得数据

SQLGetData(hStmt SQL_CHAR ppcStation[nRow] lColLen &lColLen);

// 行标随行的Fetch递增

++nRow;

}

// 现在我们就取出了所有TrainID和AllStations

// 关闭数据库连接释放资源

SQLFreeHandle(SQL_HANDLE_STMT hStmt);

SQLDisConnect(hDbc);

SQLFreeHandle(SQL_HANDLE_DBC hDbc);

SQLFreeHandle(SQL_HANDLE_ENV hEnv);               

上一篇:C#实现的多线程异步数据包接收器框架

下一篇:使用.NET从VB6中访问事件日志