Tedious: Запросы можно делать только в состоянии LoggedIn, а не в состоянии SentClientRequest (код: «EINVALIDSTATE»).

Созданный на 28 сент. 2016  ·  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!");
                }
            }
        )
    }
});

Самый полезный комментарий

Для людей, пришедших сюда из Google (как и я): пул tedious-connection предлагает отличный способ борьбы с этой проблемой. Использование pool.acquire((err, connection) => ...) и connection.release() — это простой способ предотвратить проблемы параллелизма. Если вам не нужны множественные подключения, настройте пул с {min: 1, max: 1} для эффективной сериализации запросов.

Все 23 Комментарий

Существует свойство «обратный вызов», которое вы можете установить по запросу, и есть событие «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' обязательно нужно задокументировать.

(Отправляю с телефона, извините за краткость).

Установка свойства обратного вызова перезапишет переданный вами обратный вызов.
в конструктор запросов. Вместо этого вы должны вызвать обратный вызов из
async внутри обратного вызова, переданного конструктору запроса. Таким образом, вы
также может правильно пересылать ошибки в async.

Ах, пропустил, что обратный вызов установлен для переданного обратного вызова. Итак, правильный путь:

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

@arthurschreiber Есть ли причина для события requestCompleted? Кажется излишним. Еще одна вещь, которую я заметил, это то, что параметр rows, передаваемый в обратный вызов, всегда пуст. Есть ли сценарий, в котором у него будут действительные данные? Я обновлю документацию.

Еще одна вещь, которую я заметил, это то, что параметр rows, передаваемый в обратный вызов, всегда пуст. Есть ли сценарий, в котором у него будут действительные данные? Я обновлю документацию.

Есть опция rowCollectionOnRequestCompletion , которую можно указать при подключении. Иметь его как глобальную опцию немного странно, я думаю, было бы разумнее иметь это для каждого запроса.

Основная идея заключается в следующем: если включена опция rowCollectionOnRequestCompletion , строки сохраняются в памяти до тех пор, пока не будет завершена соответствующая Request (и в настоящее время, я думаю, они даже сохраняются до тех пор, пока не будет выполнено Request ). сильно повлиять на производительность. Предпочтительный подход — просто использовать обратный вызов rows , а если вам нужно собрать все строки в массив, вы можете сделать это самостоятельно.

Я не думаю, что это оптимально, вероятно, было бы лучше иметь этот настраиваемый для каждого запроса вместо каждого соединения, но конструктор Request еще не принимает никаких параметров, поэтому сигнатура метода должна быть измененный.

Любая причина для события «requestCompleted»?

Я не уверен - это было добавлено до того, как я начал работать над утомительным.

Открыт #459 для отслеживания проблемы rowCollectionOnRequestCompletion. Я уверен, что мы можем добавить и справиться с этим
обратно совместимым способом.

Установка свойства обратного вызова перезапишет переданный вами обратный вызов.
в конструктор запросов. Вместо этого вы должны вызвать обратный вызов из
async внутри обратного вызова, переданного конструктору запроса. Таким образом, вы
также может правильно пересылать ошибки в async.

@arthurschreiber Ты прав. Это правильный способ не переопределять результаты/ошибки. Работает как шарм :-)

Создан #464 для улучшения документации по Event: 'requestCompleted'

Для людей, пришедших сюда из Google (как и я): пул tedious-connection предлагает отличный способ борьбы с этой проблемой. Использование pool.acquire((err, connection) => ...) и connection.release() — это простой способ предотвратить проблемы параллелизма. Если вам не нужны множественные подключения, настройте пул с {min: 1, max: 1} для эффективной сериализации запросов.

Кто-нибудь, пожалуйста, предложите, режим проверки подлинности Windows не работает для подключения nodejs2Sql.

@wafaabbass tedious еще не поддерживает встроенную аутентификацию Windows. Мы работаем над этим, вы можете отслеживать его в # 415.

Всем привет, у меня такая же проблема.

отлаживать:

Запросы могут быть сделаны только в состоянии LoggedIn, а не в состоянии SentClientRequest.
(узел: 5180) UnhandledPromiseRejectionWarning: отклонение необработанного обещания (идентификатор отклонения: 1): RequestError: запросы могут выполняться только в состоянии LoggedIn, а не в состоянии SentClientRequest
(узел: 5180) [DEP0018] Предупреждение об устаревании: отказы от необработанных обещаний устарели. В будущем отказы от обещаний, которые не будут обработаны, завершат процесс 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, у меня возникает та же проблема. Большое спасибо.

На случай, если кто-то сочтет это полезным, поскольку Google сначала возвращает эту страницу для этого сообщения об ошибке.
Для моей службы, основанной на одном соединении, сработало создание оболочки на основе 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 и запросом на вставку, даже после того, как я дождусь requestCompleted после запроса sql.

Распространенной причиной этой ошибки является то, что _только один запрос может выполняться для соединения одновременно_. Вам нужно дождаться выполнения обратного вызова запроса либо с ошибкой, либо с результатом, прежде чем делать другой запрос. Пожалуйста, посетите страницу часто задаваемых вопросов здесь

Так как на этом форуме, кажется, слишком много разных вопросов. Ошибка 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);

Я публикую полное решение, используя обратные вызовы для этой проблемы. У меня была такая же проблема, и, глядя на Google, я оказался здесь. Я тестировал этот код с помощью скрипта, выполняющего 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);
});

Я публикую полное решение, используя обратные вызовы для этой проблемы. У меня была такая же проблема, и, глядя на Google, я оказался здесь. Я тестировал этот код с помощью скрипта, выполняющего 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 рейтинги