Tedious: As solicitações só podem ser feitas no estado LoggedIn, não no estado SentClientRequest (código: 'EINVALIDSTATE')

Criado em 28 set. 2016  ·  23Comentários  ·  Fonte: tediousjs/tedious

No meu exemplo abaixo, estou tentando executar serialmente uma matriz de funções. Mas os Requests só podem ser feitos no estado LoggedIn, então tive que verificar esse connection.state !== connection.STATE.LOGGED_IN . E como você pode ver na função Read abaixo, tive que colocar a solicitação de volta na fila para se livrar do erro.

{ [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' }

Existe uma maneira melhor de conseguir isso? Vejo que questões semelhantes foram levantadas antes de #19 e #355 . Existe uma maneira recomendada de executar várias solicitações em uma conexão?

Código:

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!");
                }
            }
        )
    }
});

Comentários muito úteis

Para as pessoas que vêm aqui do Google (como eu): tedious-connection-pool oferece uma ótima maneira de combater esse problema. Usar pool.acquire((err, connection) => ...) e connection.release() é uma maneira fácil de evitar problemas de simultaneidade. Se você não quiser várias conexões, configure o pool com {min: 1, max: 1} para serializar consultas com eficiência.

Todos 23 comentários

Existe uma propriedade 'callback' que você pode definir no Request e há um evento 'requestCompleted' no Request, ambos parecem funcionar. E essas são as duas coisas que não tentamos :)

Pode haver alguma limpeza e documentação necessária sobre isso.

Experimente estes e veja se funciona.

Usando 'retorno de chamada':

  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.
      }
  };

Usando 'requestCompleted':

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

Na verdade, colocarei essas notas em https://github.com/tediousjs/tedious/issues/61 e fecharei esta, pois é o mesmo problema.

Reabrindo o problema. https://github.com/tediousjs/tedious/issues/61 é totalmente independente. As questões referidas por @SaloniSonpal estão realmente encerradas. Então, reabrindo isso para rastreamento.

Isso precisa de alguma limpeza e documentação.

Obrigado @tvrprasad. Ambos funcionaram e são muito mais limpos. A abordagem callback oferece a vantagem de lidar com o erro. Event: 'requestCompleted' definitivamente precisa ser documentado.

(Enviando do meu telefone, desculpe a concisão).

Definir a propriedade de retorno de chamada substituirá o retorno de chamada que você passou
para o construtor Request. Em vez disso, você deve chamar o retorno de chamada de
async dentro do retorno de chamada passado para o construtor Request. Desta forma você
também pode encaminhar erros corretamente para assíncrono.

Ah, perdido esse retorno de chamada está definido para o retorno de chamada passado. Então, o caminho certo seria:

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

@arthurschreiber Algum motivo para o evento 'requestCompleted'? Parece redundante. Outra coisa que noto é que o parâmetro rows passado para o retorno de chamada está sempre vazio. Existe um cenário em que ele terá dados válidos? Vou atualizar a documentação.

Outra coisa que noto é que o parâmetro rows passado para o retorno de chamada está sempre vazio. Existe um cenário em que ele terá dados válidos? Vou atualizar a documentação.

Existe a opção rowCollectionOnRequestCompletion que pode ser especificada ao conectar. Tê-lo como uma opção global é um pouco estranho, acho que faria mais sentido ter isso por solicitação.

A ideia principal por trás disso é a seguinte: Se a opção rowCollectionOnRequestCompletion estiver habilitada, as linhas são armazenadas na memória até que o Request correspondente seja concluído (e, atualmente, acho que elas são mantidas até o Request recebe GC). Com conjuntos de resultados pequenos, isso geralmente não é um problema, mas com conjuntos de resultados grandes, isso pode afetar muito negativamente o desempenho . A abordagem preferida é usar apenas o retorno de chamada rows e, se você precisar coletar todas as linhas em uma matriz, poderá fazê-lo sozinho.

Eu não acho que isso seja o ideal, ter essa configuração por solicitação em vez de por conexão provavelmente seria melhor - mas o construtor Request ainda não aceita nenhuma opção, então a assinatura do método teria que ser mudado.

Algum motivo para o evento 'requestCompleted'?

Não tenho certeza - isso foi adicionado antes de eu começar a trabalhar no tedioso.

Aberto #459 para rastrear o problema rowCollectionOnRequestCompletion. Tenho certeza de que podemos adicionar e lidar com isso
de forma compatível com versões anteriores.

Definir a propriedade de retorno de chamada substituirá o retorno de chamada que você passou
para o construtor Request. Em vez disso, você deve chamar o retorno de chamada de
async dentro do retorno de chamada passado para o construtor Request. Desta forma você
também pode encaminhar erros corretamente para assíncrono.

@arthurschreiber Você está certo. Essa é a maneira correta de não substituir os resultados/erros. Funciona como um encanto :-)

Criado #464 para melhorar a documentação em Event: 'requestCompleted'

Para as pessoas que vêm aqui do Google (como eu): tedious-connection-pool oferece uma ótima maneira de combater esse problema. Usar pool.acquire((err, connection) => ...) e connection.release() é uma maneira fácil de evitar problemas de simultaneidade. Se você não quiser várias conexões, configure o pool com {min: 1, max: 1} para serializar consultas com eficiência.

Alguém por favor sugira, o modo de autenticação do Windows não está funcionando para conectividade nodejs2Sql.

@wafaabbass tedious ainda não suporta autenticação integrada do Windows. Estamos trabalhando nisso, você pode rastreá-lo em #415.

Oi pessoal, estou com o mesmo problema.

depurar:

As solicitações só podem ser feitas no estado LoggedIn, não no estado SentClientRequest
(nó:5180) UnhandledPromiseRejectionWarning: Rejeição de promessa não tratada (ID de rejeição: 1): RequestError: Solicitações só podem ser feitas no estado LoggedIn, não no estado SentClientRequest
(node:5180) [DEP0018] DeprecationWarning: As rejeições de promessa não tratadas estão obsoletas. No futuro, as rejeições de promessas que não forem tratadas encerrarão o processo Node.js com um código de saída diferente de zero.

Já está resolvido? Este é o meu código.

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()

}

Se eu chamar willIGetArea e willIGetSymptom, tudo isso funciona, mas, se eu chamar com willGetModifier, também tenho o mesmo problema. Muito obrigado.

Caso alguém ache útil, já que o Google retorna esta página primeiro para esta mensagem de erro.
Funcionou para meu serviço baseado em conexão única para criar um wrapper baseado em Promise para a conexão. Não tenho certeza se é uma boa solução em geral, pois não sou um desenvolvedor js experiente:

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;
    }
}

E a própria consulta pode ser executada assim:

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);
    });
}

Portanto, getObjList agora pode ser executado a qualquer momento sem verificar o estado da conexão.

A preocupação aqui é boa. No meu código eu faço uma solicitação de conexão, uma solicitação sql e depois uma solicitação de inserção. Ocorre um erro na solicitação de inserção, a menos que eu faça outra solicitação de conexão entre o sql e a solicitação de inserção, mesmo depois de esperar pelo requestCompleted após a solicitação do sql.

Um motivo comum para esse erro é que _somente uma consulta pode ser executada em uma conexão por vez_. Você precisa esperar até que o callback da solicitação seja executado, seja com um erro ou com o resultado, antes de fazer outra solicitação. Por favor, visite a página de perguntas frequentes aqui

Uma vez que parece haver muitos vários problemas neste fórum. O erro Requests can only be made in the LoggedIn state, not the SentClientRequest state pode ser causado por vários motivos diferentes. Se você tiver esse erro e não achar a página de perguntas frequentes útil, levante seu próprio problema com as seguintes informações para que possamos resolvê-lo com mais precisão:

  • Versão tediosa
  • Sua configuração para Conexão
  • Um script reproduzível

Obrigado! 😄

Consegui resolver isso, o problema estava do meu lado, não percebi que você precisa de uma conexão separada para cada solicitação

@mgarf Qual foi a sua solução? Também não percebi que preciso de uma conexão separada para cada solicitação. Estou tendo problemas de simultaneidade como os outros nesta discussão.

@mikebutak você não precisa necessariamente de uma conexão separada para cada solicitação. Você só precisa de uma conexão lidando com uma solicitação _de cada vez_. Para lidar com várias solicitações em uma conexão, você precisa executar uma nova solicitação no retorno de chamada da anterior, após o término. Por exemplo,

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);

Estou postando uma solução completa usando retornos de chamada para esse problema. Eu tive o mesmo problema e ao olhar no google acabei aqui. Eu testei este código com um script executando 1 chamada http a cada segundo e também meio segundo sem problemas.

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);
});

Estou postando uma solução completa usando retornos de chamada para esse problema. Eu tive o mesmo problema e ao olhar no google acabei aqui. Eu testei este código com um script executando 1 chamada http a cada segundo e também meio segundo sem problemas.

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);
});

Esta solução funciona, mas é uma implementação válida? Quão viável é isso, considerando que pode haver centenas de milhares de inserts/selects e criando uma conexão para cada um deles? Obrigado pela solução btw, estou apenas tentando entender se é uma solução viável. Especialmente se estivermos usando uma Função do Azure?

Esta página foi útil?
0 / 5 - 0 avaliações