Tedious: Les requêtes ne peuvent être effectuées qu'à l'état LoggedIn, pas à l'état SentClientRequest (code : 'EINVALIDSTATE')

Créé le 28 sept. 2016  ·  23Commentaires  ·  Source: tediousjs/tedious

Dans mon exemple ci-dessous, j'essaie d'exécuter en série un tableau de fonctions. Mais les demandes ne peuvent être faites que dans l'état LoggedIn, j'ai donc dû vérifier que connection.state !== connection.STATE.LOGGED_IN . Et comme vous pouvez le voir dans la fonction Lire ci-dessous, il a fallu remettre la requête en file d'attente afin de se débarrasser de l'erreur.

{ [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-t-il un meilleur moyen d'y parvenir? Je vois que des problèmes similaires ont été soulevés avant #19 et #355 . Existe-t-il une méthode recommandée pour exécuter plusieurs requêtes sur une connexion ?

Code:

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

Commentaire le plus utile

Pour les personnes venant ici de Google (comme moi) : tedious-connection-pool offre un excellent moyen de lutter contre ce problème. L'utilisation pool.acquire((err, connection) => ...) et connection.release() est un moyen simple d'éviter les problèmes de concurrence. Si vous ne souhaitez pas plusieurs connexions, configurez le pool avec {min: 1, max: 1} pour sérialiser efficacement les requêtes.

Tous les 23 commentaires

Il y a une propriété 'callback' que vous pouvez définir sur Request et il y a un événement 'requestCompleted' sur Request, les deux semblent fonctionner. Et ce sont les deux choses que nous n'avons pas essayées :)

Il peut y avoir un nettoyage et une documentation nécessaires à ce sujet.

Essayez-les et voyez si cela fonctionne.

Utilisation de 'rappel' :

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

Utilisation de 'requestCompleted' :

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

Je vais en fait mettre ces notes dans https://github.com/tediousjs/tedious/issues/61 et fermer celle-ci car c'est le même problème.

Réouverture du sujet. https://github.com/tediousjs/tedious/issues/61 est totalement indépendant. Les problèmes évoqués par @SaloniSonpal sont en fait clos. Alors rouvrez ceci pour le suivi.

Cela nécessite un peu de nettoyage et de documentation.

Merci @tvrprasad. Les deux ont fonctionné et sont beaucoup plus propres. L'approche callback l'avantage de gérer l'erreur. Event: 'requestCompleted' doit absolument être documenté.

(Envoi depuis mon téléphone, excusez le laconisme).

La définition de la propriété de rappel écrasera le rappel que vous avez passé
au constructeur Request. Au lieu de cela, vous devez appeler le rappel à partir de
async à l'intérieur du rappel passé au constructeur de la requête. De cette façon, vous
peut également transmettre correctement les erreurs à async.

Ah, raté, ce rappel est défini sur le rappel passé. Donc, la bonne façon serait:

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

@arthurschreiber Une raison pour l'événement 'requestCompleted' ? Semble redondant. Une autre chose que je remarque est que le paramètre rows transmis au rappel est toujours vide. Existe-t-il un scénario où il aura des données valides ? Je mettrai à jour la documentation.

Une autre chose que je remarque est que le paramètre rows transmis au rappel est toujours vide. Existe-t-il un scénario où il aura des données valides ? Je mettrai à jour la documentation.

Il y a l'option rowCollectionOnRequestCompletion qui peut être spécifiée lors de la connexion. L'avoir comme option globale est un peu bizarre, je pense qu'il serait plus logique d'avoir cette demande par demande.

L'idée principale derrière cela est la suivante : si l'option rowCollectionOnRequestCompletion est activée, les lignes sont stockées en mémoire jusqu'à ce que le Request correspondant soit terminé (et actuellement, je pense qu'elles sont même conservées jusqu'à ce que le Request obtient gc'ed). Avec de petits ensembles de résultats, ce n'est généralement pas un problème, mais avec de grands ensembles de résultats, cela peut avoir un impact négatif important sur les performances . L'approche préférée consiste à utiliser simplement le rappel rows , et si vous avez besoin de collecter toutes les lignes dans un tableau, vous pouvez le faire vous-même.

Je ne pense pas que ce soit optimal, avoir cette requête configurable au lieu de par connexion serait probablement mieux - mais le constructeur Request ne prend pas encore d'options, donc la signature de la méthode devrait être modifié.

Une raison pour l'événement 'requestCompleted' ?

Je ne suis pas sûr - cela a été ajouté avant que je commence à travailler sur fastidieux.

Ouvert # 459 pour suivre le problème rowCollectionOnRequestCompletion. Je suis sûr que nous pouvons ajouter et gérer cela
d'une manière rétrocompatible.

La définition de la propriété de rappel écrasera le rappel que vous avez passé
au constructeur Request. Au lieu de cela, vous devez appeler le rappel à partir de
async à l'intérieur du rappel passé au constructeur de la requête. De cette façon, vous
peut également transmettre correctement les erreurs à async.

@arthurschreiber Vous avez raison. C'est la bonne façon de ne pas remplacer les résultats/erreurs. Fonctionne comme un charme :-)

Créé #464 pour améliorer la documentation sur Event: 'requestCompleted'

Pour les personnes venant ici de Google (comme moi) : tedious-connection-pool offre un excellent moyen de lutter contre ce problème. L'utilisation pool.acquire((err, connection) => ...) et connection.release() est un moyen simple d'éviter les problèmes de concurrence. Si vous ne souhaitez pas plusieurs connexions, configurez le pool avec {min: 1, max: 1} pour sérialiser efficacement les requêtes.

N'importe qui s'il vous plaît suggérer, le mode d'authentification Windows ne fonctionne pas pour la connectivité nodejs2Sql.

@wafaabbass tedious ne prend pas encore en charge l'authentification intégrée de Windows. Nous y travaillons, vous pouvez le suivre dans #415.

Salut tout le monde, j'ai le même problème.

déboguer:

Les demandes ne peuvent être effectuées que dans l'état LoggedIn, pas dans l'état SentClientRequest
(nœud : 5180) UnhandledPromiseRejectionWarning : rejet de la promesse non gérée (identifiant de rejet : 1) : RequestError : les demandes ne peuvent être effectuées qu'à l'état LoggedIn, pas à l'état SentClientRequest
(node:5180) [DEP0018] DeprecationWarning : les rejets de promesses non gérées sont obsolètes. À l'avenir, les rejets de promesses qui ne sont pas gérés mettront fin au processus Node.js avec un code de sortie différent de zéro.

Est-ce déjà résolu ? C'est mon code.

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

}

Si j'appelle willIGetArea et willIGetSymptom, tout cela fonctionne mais, si je l'appelle avec willGetModifier, j'ai également le même problème. Merci beaucoup.

Au cas où quelqu'un le trouverait utile, puisque Google renvoie d'abord cette page pour ce message d'erreur.
Cela a fonctionné pour mon service basé sur une connexion unique pour créer un wrapper basé sur Promise pour la connexion. Je ne suis pas sûr que ce soit une bonne solution en général, car je ne suis pas un développeur js expérimenté:

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

Et la requête elle-même peut être exécutée comme ceci :

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

Ainsi, getObjList peut maintenant être exécuté à tout moment sans vérifier l'état de la connexion.

Le souci ici est bien. Dans mon code, je fais une demande de connexion, une demande sql, puis une demande d'insertion. Il se trompe sur la demande d'insertion à moins que je ne fasse une autre demande de connexion entre sql et la demande d'insertion même après avoir attendu le requestCompleted après la demande sql.

Une raison courante de cette erreur est que _seule une requête peut être exécutée sur une connexion à la fois_. Vous devez attendre que le rappel de la demande soit exécuté, soit avec une erreur, soit avec le résultat avant de faire une autre demande. Veuillez visiter la page FAQ ici

Comme il semble y avoir trop de problèmes divers sur ce forum. L'erreur Requests can only be made in the LoggedIn state, not the SentClientRequest state peut être causée par différentes raisons. Si vous rencontrez cette erreur et que vous n'avez pas trouvé la page FAQ utile, veuillez soulever votre propre problème avec les informations suivantes afin que nous puissions résoudre votre problème avec plus de précision :

  • Version fastidieuse
  • Votre configuration pour la connexion
  • Un scénario reproductible

Merci! 😄

A pu résoudre ce problème, le problème était de mon côté, je ne savais pas que vous aviez besoin d'une connexion distincte pour chaque demande

@mgarf Quelle était votre solution ? Je ne savais pas non plus que j'avais besoin d'une connexion distincte pour chaque demande. J'ai des problèmes de simultanéité comme les autres dans cette discussion.

@mikebutak vous n'avez pas nécessairement besoin d'une connexion distincte pour chaque demande. Vous n'avez besoin que d'une seule connexion traitant une requête _à la fois_. Pour gérer plusieurs requêtes sur une connexion, vous devez exécuter une nouvelle requête dans le rappel de la précédente, une fois celle-ci terminée. Par exemple,

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

Je poste une solution complète en utilisant des rappels pour ce problème. J'ai eu le même problème et en regardant google je me suis retrouvé ici. J'ai testé ce code avec un script exécutant 1 appel http toutes les secondes et également une demi-seconde sans aucun problème.

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

Je poste une solution complète en utilisant des rappels pour ce problème. J'ai eu le même problème et en regardant google je me suis retrouvé ici. J'ai testé ce code avec un script exécutant 1 appel http toutes les secondes et également une demi-seconde sans aucun problème.

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

Cette solution fonctionne mais est-ce une implémentation valide ? Dans quelle mesure est-ce faisable, étant donné qu'il pourrait y avoir des centaines de milliers d'insertions/sélections et créer une connexion pour chacun d'eux ? Merci pour la solution btw, j'essaie juste de comprendre si c'est une solution viable. Surtout si nous utilisons une fonction Azure ?

Cette page vous a été utile?
0 / 5 - 0 notes