Tedious: 请求只能在 LoggedIn 状态下进行,不能在 SentClientRequest 状态下进行(代码:'EINVALIDSTATE')

创建于 2016-09-28  ·  23评论  ·  资料来源: tediousjs/tedious

在下面的示例中,我试图串行执行一组函数。 但是请求只能在 LoggedIn 状态下进行,所以我必须检查那个connection.state !== connection.STATE.LOGGED_IN 。 正如您在下面的读取函数中看到的那样,必须将请求放回队列中才能消除错误。

{ [RequestError: Requests can only be made in the LoggedIn state, not the SentClientRequest state]
  message: 'Requests can only be made in the LoggedIn state, not the SentClientRequest state',
  code: 'EINVALIDSTATE' }

有没有更好的方法来实现这一目标? 我看到在#19#355之前提出了类似的问题。 是否有在连接上执行多个请求的推荐方法?

代码:

var Connection = require('tedious').Connection;  
var Request = require('tedious').Request;  
var TYPES = require('tedious').TYPES;  
var async = require('async');

// Create connection to database
var config = {
        userName: 'mylogin',  
        password: 'mypassword',  
        server: 'localhost',
        options: {
            // encrypt: true, /*If you are connecting to a Microsoft Azure SQL database, you will need this*/
            database: 'testdb'
        }
    }
var connection = new Connection(config);  

// Attempt to connect and execute queries if connection goes through
connection.on('connect', function(err) {  
    if (err) {
        console.log(err);
    }  
    else    {
        console.log("Connected");

        // Execute all functions in the array serially
        async.waterfall([
            function Start(callback){
                console.log("Starting...");
                callback(null, 'Jake', 'United States');
            },
            function Insert(name, location, callback){
                console.log("Inserting into Table...");

                request = new Request("INSERT dbo.employees (name, location) OUTPUT INSERTED.id VALUES (<strong i="14">@Name</strong>, @Location);", function(err){  
                     if (err) {
                            console.log(err);
                        }  
                    }
                );  
                request.addParameter('Name', TYPES.NVarChar, name);  
                request.addParameter('Location', TYPES.NVarChar, location);  

                request.on('row', function(columns) {  
                    columns.forEach(function(column) {  
                      if (column.value === null) {  
                        console.log('NULL');  
                      } else {  
                        console.log("Employee id inserted is " + column.value);  
                      }  
                    });  
                }); 

                // Check how many rows were inserted
                request.on('doneInProc', function(rowCount, more) {  
                    console.log(rowCount + ' row(s) inserted');  
                    callback(null, 'Jared');
                });             

                connection.execSql(request);  
            },
            function Read(callback){
                // Requests can only be made in the LoggedIn state, so check for that
                if (connection.state !== connection.STATE.LOGGED_IN) {
                    // Put the request back on the dispatcher if connection is not in LoggedIn state
                    setTimeout(Read, 0, callback);
                    return;
                }

                console.log("Reading rows from the Table...");

                // Create the request to read from table
                request = new Request("SELECT * FROM dbo.employees;", function(err) {  
                    if (err) {
                        console.log(err);
                    }  
                });  

                // Output the number of rows read 
                request.on('doneInProc', function (rowCount, more, rows) {  
                    console.log(rowCount + ' row(s) returned');  
                    callback(null);
                });  

                connection.execSql(request);  
            }],
            function Complete(err, result) {
                if(err) {
                    console.log("Error:", err);
                }
                else {
                    console.log("Done!");
                }
            }
        )
    }
});

最有用的评论

对于从谷歌来到这里的人(就像我一样):乏味的连接池提供了一种解决这个问题的好方法。 使用pool.acquire((err, connection) => ...)connection.release()是防止并发问题的简单方法。 如果您不想要多个连接,请使用{min: 1, max: 1}配置池以有效地序列化查询。

所有23条评论

您可以在 Request 上设置一个“回调”属性,并且在 Request 上有一个“requestCompleted”事件,两者似乎都有效。 这些是我们没有尝试过的两件事:)

这可能需要一些清理和文档。

试试这些,看看它是否有效。

使用“回调”:

  request.callback = function (err, rowCount, rows) {
      // rows is not being set but rowCount is. May be a bug.
      if (err) {
        // Error handling.
      } else {
        // Next SQL statement.
      }
  };

使用“请求完成”:

  request.on('requestCompleted', function () {
      // Next SQL statement.
  });

我实际上会把这个笔记放在https://github.com/tediousjs/tedious/issues/61并关闭这个笔记,因为它是同一个问题。

重新打开问题。 https://github.com/tediousjs/tedious/issues/61完全不相关。 @SaloniSonpal提到的问题实际上已经关闭。 所以重新打开它进行跟踪。

这需要一些清理和文档。

谢谢@tvrprasad。 这两种方法都有效,而且更清洁。 callback方法确实具有处理错误的优势。 Event: 'requestCompleted'肯定需要记录在案。

(从我的手机发送,请原谅简洁)。

设置回调属性将覆盖您传递的回调
到 Request 构造函数。 相反,您应该从
回调中的异步传递给 Request 构造函数。 这样你
也可以正确地将错误转发到异步。

啊,错过了回调设置为传入的回调。所以正确的方法是:

  request = new Request(sqlQuery, function (err, rowCount, rows) {
    if (err) {
      // Error handling.
    } else {
      // Next SQL statement.
    }
  });

@arthurschreiber 'requestCompleted' 事件的任何原因? 似乎是多余的。 我注意到的另一件事是传递给回调的行参数始终为空。 是否存在具有有效数据的情况? 我会更新文档。

我注意到的另一件事是传递给回调的行参数始终为空。 是否存在具有有效数据的情况? 我会更新文档。

连接时可以指定rowCollectionOnRequestCompletion选项。 将它作为一个全局选项有点奇怪,我认为每个请求都有这个更有意义。

这背后的主要思想如下:如果启用了rowCollectionOnRequestCompletion选项,则行将存储在内存中,直到相应的Request完成(目前,我认为它们甚至会保留到Request被 gc'ed)。 对于小的结果集,这通常不是问题,但对于大的结果集,这会对性能产生很大的负面影响。 首选方法是只使用rows回调,如果您需要将所有行收集到一个数组中,您可以自己这样做。

我不认为这是最优的,拥有这个可配置的每个请求而不是每个连接可能会更好 - 但是Request构造函数还没有接受任何选项,所以方法签名必须是改变了。

'requestCompleted' 事件的任何原因?

我不确定 - 这是在我开始繁琐的工作之前添加的。

打开 #459 以跟踪 rowCollectionOnRequestCompletion 问题。 我相信我们可以添加并处理它
以向后兼容的方式。

设置回调属性将覆盖您传递的回调
到 Request 构造函数。 相反,您应该从
回调中的异步传递给 Request 构造函数。 这样你
也可以正确地将错误转发到异步。

@arthurschreiber你是对的。 这是不覆盖结果/错误的正确方法。 奇迹般有效 :-)

创建 #464 以改进Event: 'requestCompleted'上的文档

对于从谷歌来到这里的人(就像我一样):乏味的连接池提供了一种解决这个问题的好方法。 使用pool.acquire((err, connection) => ...)connection.release()是防止并发问题的简单方法。 如果您不想要多个连接,请使用{min: 1, max: 1}配置池以有效地序列化查询。

任何人请建议,Windows 身份验证模式不适用于 nodejs2Sql 连接。

@wafaabbass tedious还不支持 Windows 集成身份验证。 我们正在努力,您可以在#415 中跟踪它。

大家好,我也有同样的问题。

调试:

请求只能在 LoggedIn 状态下进行,不能在 SentClientRequest 状态下进行
(node:5180) UnhandledPromiseRejectionWarning: UnhandledPromiseRejectionWarning: Unhandled Promise reject (rejection id: 1): RequestError: Requests can only be made in LoggedIn state, not the SentClientRequest state
(节点:5180)[DEP0018] DeprecationWarning:不推荐使用未处理的承诺拒绝。 将来,未处理的 Promise 拒绝将使用非零退出代码终止 Node.js 进程。

还解决了吗? 这是我的代码。

function createAsPromiseWithTransaction(callback, data, connection) {

  const transactionReady = (err) => {
    if (err) {
      return callback(err)
    }
    return callback(null)
  }

  const transactionFailed = (originalErr) =>
    (err) => {
      if (err) {
        return callback(err)
      }
      return callback(originalErr)
    }

  const willIGetArea = new Promise(
    (resolve, reject) => {
      const create = function(data, connection) {
        let areaIdInserted = null
        const request = new Request(CONSTANTS.SPNames.Area,
          (err, rowCount, rows) => err ? reject(err) : resolve(areaIdInserted))

        request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
        request.addParameter('ParentArea', TYPES.Int, data.parentArea)
        request.addParameter('Name', TYPES.VarChar, data.nameArea)
        request.addParameter('ShortName', TYPES.VarChar, data.shortNameArea)
        request.addParameter('Description', TYPES.VarChar, data.descriptionArea)
        request.addParameter('Gender', TYPES.Bit, data.gender)
        request.addParameter('Colour', TYPES.VarChar, data.colour)
        request.addParameter('X', TYPES.Int, data.x)
        request.addParameter('Y', TYPES.Int, data.y)
        request.addParameter('Active', TYPES.Int, data.active)

        request.on('row', function(columns) {
          areaIdInserted = columns[1].value
        });

        connection.callProcedure(request)
      }

      const transactionStarted = function(err) {
        if (err) return callback(err)
        create(data, connection)
      }

      connection.beginTransaction(transactionStarted)
    })

  const willIGetSymptom = function(areaId) {

    return new Promise(function(resolve, reject) {

      const create = function(data, connection, areaId) {
        let symptomIdInserted = null
        const request = new Request(CONSTANTS.SPNames.Symptom,
          (err, rowCount, rows) => err ? reject(err) : resolve(symptomIdInserted))

        request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
        request.addParameter('AreaId', TYPES.BigInt, areaId)
        request.addParameter('Code', TYPES.VarChar, data.code)
        request.addParameter('Name', TYPES.VarChar, data.nameSymptom)
        request.addParameter('ShortName', TYPES.VarChar, data.shortNameSymptom)
        request.addParameter('Description', TYPES.VarChar, data.descriptionSymptom)
        request.addParameter('Gender', TYPES.Bit, data.gender)
        request.addParameter('Active', TYPES.Bit, data.active)

        request.on('row', function(columns) {
          symptomIdInserted = columns[1].value
        });


        connection.callProcedure(request)
      }

      create(data, connection, areaId)

    })
  }

  const willIGetModifier = new Promise(function(resolve, reject) {

    const request = new Request(
      CONSTANTS.SPNames.Modifier,
      function(err, rowCount, rows) {
        if (err) {
          reject(err)
        } else {
          resolve(rowCount,rows)
        }
      })

    request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
    request.addParameter('Name', TYPES.VarChar, data.nameModifier)
    request.addParameter('Order', TYPES.SmallInt, data.order)
    request.addParameter('Active', TYPES.Bit, data.active)

    connection.callProcedure(request)

  })

  const runPromise = () =>
    willIGetArea
    .then( (areaId) =>
      willIGetSymptom(areaId)
      .then( (symptomId) =>
        willIGetModifier
        .then((rowCount,rows) => connection.commitTransaction(transactionReady))
        .catch((err) => connection.rollbackTransaction(transactionFailed(err)))
      )
      .catch((err) => connection.rollbackTransaction(transactionFailed(err)))
    )it 
    .catch((err) => connection.rollbackTransaction(transactionFailed(err)))

  runPromise()

}

如果我调用 willIGetArea 和 willIGetSymptom,所有这些都有效,但是如果我使用 willGetModifier 调用它,我也会遇到同样的问题。 非常感谢。

万一有人发现它有帮助,因为谷歌会首先返回此页面以获取此错误消息。
它适用于我的基于单连接的服务,为连接创建基于 Promise 的包装器。 不过,不确定这是否是一个好的解决方案,因为我不是经验丰富的 js 开发人员:

class PromisedConnection {
    constructor(config) {
        // this.connection always points to a promise resolving after the last assigned request
        // initial setting is either resolved once a new connection is established or rejected if an error occurs
        this.connection = new Promise((resolve, reject) => {
            const dbConnection = new Connection(config);
            dbConnection.on("connect", function (err) {
                if (err) {
                    reject(err);
                }
                else {
                    resolve(dbConnection);
                }
            });
        });
    }

    execute(request) {
        const nextConnection = new Promise((resolve, reject) => {
            // after scheduling new request this.connection should be reassigned to be the last in promise queue
            this.connection
                .catch( (reason) => {
                    reject(reason);
                } )
                .then( (dbConnection) => { // a new request can be executed only within the connection is free again (resolved after the last request)
                    request.on("requestCompleted", () => { // add an additional event listener in order to release connection after the request is done
                        resolve(dbConnection);
                    });
                    dbConnection.execSql(request);
                });
        } );
        this.connection = nextConnection;
    }
}

查询本身可以这样执行:

const dbConnection = new PromisedConnection(config);

function getObjList(query, parameters = []) {
    return new Promise((resolve, reject) => {
        const objList = [];
        let request = new Request(
            query,
            function (err, rowCount, rows) {
                if (err) {
                    reject(err);
                } else if (rowCount < 1) {
                    reject(new Error("0 rows returned from DB"));
                }
            }
        );
        for (const {name, type, value} of parameters) {
            request.addParameter(name, type, value);
        };

        request.on("row", (columns) => {
            objList.push(Obj.fromColumns(columns)); // here I just make a specific object from each row
        });

        request.on("requestCompleted", () => {
            resolve(objList);
        });

        dbConnection.execute(request);
    });
}

所以现在可以随时执行getObjList而无需检查连接状态。

这里的关注是好的。 在我的代码中,我执行了一个连接请求、一个 sql 请求,然后是一个插入请求。 它在插入请求上出错,除非我在 sql 和插入请求之间进行另一个连接请求,即使在我等待 sql 请求之后的requestCompleted之后也是如此。

此错误的一个常见原因是_一次只能在一个连接上执行一个查询_。 在发出另一个请求之前,您需要等到请求回调被执行,无论是错误还是结果。 请在此处访问常见问题解答页面

由于这个论坛上似乎有太多各种各样的问题。 错误Requests can only be made in the LoggedIn state, not the SentClientRequest state可能由多种不同的原因引起。 如果您遇到此错误并且您认为常见问题解答页面没有帮助,请使用以下信息提出您自己的问题,以便我们可以更准确地解决您的问题:

  • 繁琐的版本
  • 您的连接配置
  • 可重现的脚本

谢谢! 😄

能够解决这个问题,问题在我这边,没有意识到每个请求都需要单独的连接

@mgarf你的解决方案是什么? 我也没有意识到我需要为每个请求单独连接。 我在这个讨论中遇到了和其他人一样的并发问题。

@mikebutak您不一定需要为每个请求单独连接。 您只需要一个连接_一次_处理一个请求。 要在一个连接上处理多个请求,您需要在完成后在前一个的回调中执行一个新请求。 例如,

function executeStatement() {
  request = new Request("select 42, 'hello world'", function(err, rowCount) {
    if (err) {
      console.log(err);
    } else {
      console.log(rowCount + ' rows');
    }

    connection.execSql(new Request("select 42, 'hello world'", function(err, rowCount) {
      if (err) {
        console.log(err);
      } else {
        console.log(rowCount + ' rows');
      }
      connection.close();
    }))
  });
  connection.execSql(request);

我正在为这个问题发布一个使用回调的完整解决方案。 我遇到了同样的问题,当我查看谷歌时,我最终来到了这里。 我使用每秒执行 1 个 http 调用的脚本对这段代码进行了负载测试,半秒也没有问题。

const {Connection, Request} = require("tedious");

const executeSQL = (sql, callback) => {
  let connection = new Connection({
    "authentication": {
      "options": {
        "userName": "USERNAME",
        "password": "PASSWORD"
      },
      "type": "default"
    },
    "server": "SERVER",
    "options": {
      "validateBulkLoadParameters": false,
      "rowCollectionOnRequestCompletion": true,
      "database": "DATABASE",
      "encrypt": true
    }
  });

  connection.connect((err) => {
    if (err)
      return callback(err, null);

    const request = new Request(sql, (err, rowCount, rows) => {
      connection.close();

      if (err)
        return callback(err, null);

      callback(null, {rowCount, rows});
    });

    connection.execSql(request);
  });
};

executeSQL("SELECT * FROM users", (err, data) => {
  if (err)
    console.error(err);

  console.log(data.rowCount);
});

//or

executeSQL("SELECT * FROM users", (err, {rowCount, rows}) => {
  if (err)
    console.error(err);

  console.log(rowCount);
});

我正在为这个问题发布一个使用回调的完整解决方案。 我遇到了同样的问题,当我查看谷歌时,我最终来到了这里。 我使用每秒执行 1 个 http 调用的脚本对这段代码进行了负载测试,半秒也没有问题。

const {Connection, Request} = require("tedious");

const executeSQL = (sql, callback) => {
  let connection = new Connection({
    "authentication": {
      "options": {
        "userName": "USERNAME",
        "password": "PASSWORD"
      },
      "type": "default"
    },
    "server": "SERVER",
    "options": {
      "validateBulkLoadParameters": false,
      "rowCollectionOnRequestCompletion": true,
      "database": "DATABASE",
      "encrypt": true
    }
  });

  connection.connect((err) => {
    if (err)
      return callback(err, null);

    const request = new Request(sql, (err, rowCount, rows) => {
      connection.close();

      if (err)
        return callback(err, null);

      callback(null, {rowCount, rows});
    });

    connection.execSql(request);
  });
};

executeSQL("SELECT * FROM users", (err, data) => {
  if (err)
    console.error(err);

  console.log(data.rowCount);
});

//or

executeSQL("SELECT * FROM users", (err, {rowCount, rows}) => {
  if (err)
    console.error(err);

  console.log(rowCount);
});

这个解决方案有效,但它是一个有效的实现吗? 考虑到可能有数十万个插入/选择并为每个插入/选择创建一个连接,这有多可行? 顺便说一句,感谢您的解决方案,我只是想了解它是否是可行的解决方案。 特别是如果我们使用的是 Azure 函数?

此页面是否有帮助?
0 / 5 - 0 等级