NodeJs——使用tedious连接 sql server

字数 901阅读 3713

项目地址:tedious

安装

npm i --save tedious

配置 config.js

基础的配置信息包括
server -- 数据库所在服务器,
userName -- 用户名,
password -- 密码,
options { database:' '} 数据库名等配置信息,详细的配置在 官方API 可以看到

let Connection= require('tedious').Connection
let Request = require('tedious').Request
let connectionCfg = {
    server: '127.0.0.1',  
    userName: 'yourname',
    password: 'yourpassword',
    options: { database: 'database' }  
  }
let connection = new Connection('connectionCfg')
connection.on('connect', function (err){
    if (!err) {
      executeStatement(querySql)
    }
})

function executeStatement ( querySql) { 
    let request = new Request(querySql , (err, rowCount)=>{
      if (err) {
        console.error(err)
        return; //创建 request 实例失败
      }
    })
    var result = [];  
    request.on('row', function(columns,idx) {
      var obj = {}  
      columns.forEach( function(column) {
        if(column.value !== null){
         var key = column.metadata.colName
         var val = column.value
          obj[key] = val
        }
      });
    result.push(obj)
    })

    request.on('done', function ( rowCount, more, rows) {
      return result
    })
}

代码写到这里感觉还是一片顺利,实际上已经有一两个小坑需要提醒一下了。

  1. requestrow 事件,回调函数中的参数 columns 受 config.options.useColumnNames 影响,默认为false,设置为true或者其他任何类型转换会判断为true的值时,返回的结果为object类型,这里的设置决定了遍历方法。详细的config配置说明在 这里
  2. requestdone事件,官方的说法是:创建Request实例时使用普通的sql查询时,查询完毕会触发该事件,请求类型为存储过程的话,触发 doneInProcdoneProc事件,实际在在使用过程中,即使将普通 sql 查询语句拼接成字符串传入 Request实例中也不会触发done事件,建议还是使用doneProc事件。语法如下:
    request.on('doneProc', function (rowCount, more, returnStatus, rows) { });
    判断callback中的more == false时,视为请求完成。

常见问题

报错
RequestError:requests can only be made in the LoggedIn state, not the SentClientRequest state
在我将配置项用作 module 引入时,经常碰到这个问题: 进入页面->触发数据库操作->返回上一页->再返回该页时,就得到一个 Internal Server Error的500页面。
翻 issue 时发现主要是因为一个问题 , 套用作者原话:issue地址

only one request can be performed on a connection at a time.

在同一时间同一个连接只能执行一个请求
具体到我的代码中,和提出 issue 这位用户很相似。同样是向外暴露了一个execute引用一个该用户核心代码和作者的解答

controller.execute = function(session, admin, sqlCommand) {
    var MSSqlConnection = require('./MSSqlConnection');

    MSSqlConnection.on('connected', function(connection){
            _performQuery(connection, sqlCommand.toString());
    });
    ...
}

作者的解释

Your MSSqlConnection variable is a singleton, because require caches modules. So when this code is called for the second page request, a second listener is added for the MSSqlConnection's connected event. When the connected event is emitted from MSSqlConnection, both listeners are called. This results in two calls to the Tedious Connection object's execSql function. The second call occurs before the first request has completed, and the state machine in Connection detects and reports the problem.

大概的意思就是,第二次进入页面时,向 connected事件加了一个监听器,connected事件触发时,两个监听器都被调用,第二个请求发起时,第一个请求还没有结束(还处于 sentClientRequest的state 中),所以就报错了。

the Request's callback signifies the completion of the request, and that another request may not be made until it is called.

创建 Request实例的意味着该请求完成,在这之前,其他请求不会被调用。
你很难去确定前面的请求是否已经是完成状态,是否可以执行下一个request,之前我不做模块化的时候,将所有请求方法和connect全部放到一个对象中,没有出现过这个问题,代码结构类似这样

let nodeSql = {
  connect: function () {
    len connection = new Connection(config)
    connection.on('connect' , funciton(){
      nodeSql.connection = connection
      ....
    })
    connection.on('end', function(){
    nodeSql.connection = null
    })
    connection.on('end', function(){
    nodeSql.connection = null
    })
  },
  execute: function (querySql) {
    if(this.connection) {
     return nodeSql.executeStatement(querySql)
    }else{
      return Promise.resolve(this.connect())
      .then(()=>nodeSql.executeStatement(querySql))
    }
   }
}

这样的代码当时没遇到问题,所以我有点懵逼,只能认定是 module的问题,为了代码结构清晰又不能放弃 module。好在作者还提供了连接池的使用方式,使用也非常简单。 tedious-connection-pool

问题解决,撒花!(写字太累了草草收尾就这样吧)

推荐阅读更多精彩内容