Tedious: Anfragen können nur im LoggedIn-Zustand gestellt werden, nicht im SentClientRequest-Zustand (Code: 'EINVALIDSTATE')

Erstellt am 28. Sept. 2016  ·  23Kommentare  ·  Quelle: tediousjs/tedious

In meinem Beispiel unten versuche ich, ein Array von Funktionen seriell auszuführen. Aber die Anfragen können nur im LoggedIn-Zustand gestellt werden, also musste ich nach diesem connection.state !== connection.STATE.LOGGED_IN suchen. Und wie Sie in der Read -Funktion unten sehen können, musste die Anfrage wieder in die Warteschlange gestellt werden, um den Fehler zu beseitigen.

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

Gibt es einen besseren Weg, dies zu erreichen? Ich sehe, dass ähnliche Probleme vor # 19 und # 355 angesprochen wurden. Gibt es eine empfohlene Methode zum Ausführen mehrerer Anfragen für eine Verbindung?

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

Hilfreichster Kommentar

Für Leute, die von Google hierher kommen (so wie ich), bietet der mühsame Verbindungspool eine großartige Möglichkeit, dieses Problem zu bekämpfen. Die Verwendung pool.acquire((err, connection) => ...) und connection.release() ist eine einfache Möglichkeit, Parallelitätsprobleme zu vermeiden. Wenn Sie nicht mehrere Verbindungen wünschen, konfigurieren Sie den Pool mit {min: 1, max: 1} , um Abfragen effektiv zu serialisieren.

Alle 23 Kommentare

Es gibt eine 'Callback'-Eigenschaft, die Sie auf Request setzen können, und es gibt ein 'requestCompleted'-Ereignis auf Request, beide scheinen zu funktionieren. Und das sind die beiden Dinge, die wir nicht ausprobiert haben :)

Möglicherweise sind dazu einige Aufräumarbeiten und Dokumentationen erforderlich.

Probieren Sie diese aus und sehen Sie, ob es funktioniert.

Verwendung von 'Rückruf':

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

Verwenden von "requestCompleted":

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

Ich werde diese Notizen tatsächlich in https://github.com/tediousjs/tedious/issues/61 einfügen und diese schließen, da es sich um dasselbe Problem handelt.

Eröffne das Thema neu. https://github.com/tediousjs/tedious/issues/61 ist völlig unabhängig davon. Die Probleme, auf die @SaloniSonpal verweist , sind tatsächlich geschlossen. Also erneutes Öffnen für die Nachverfolgung.

Dies erfordert einige Aufräumarbeiten und Dokumentation.

Danke @tvrprasad. Beides hat funktioniert und ist viel sauberer. Der callback -Ansatz bietet den Vorteil, dass der Fehler behandelt wird. Event: 'requestCompleted' muss unbedingt dokumentiert werden.

(Sende von meinem Handy, entschuldigen Sie die Knappheit).

Durch das Festlegen der Callback-Eigenschaft wird der übergebene Callback überschrieben
an den Request-Konstruktor. Stattdessen sollten Sie den Rückruf von anrufen
async innerhalb des Callbacks, der an den Request-Konstruktor übergeben wird. Auf diese Weise Sie
kann auch Fehler korrekt an async weiterleiten.

Ah, verpasst, dass Callback auf den Callback übergeben gesetzt wird. Der richtige Weg wäre also:

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

@arthurschreiber Gibt es einen Grund für das Ereignis „requestCompleted“? Scheint überflüssig. Eine andere Sache, die mir auffällt, ist, dass der an den Callback übergebene Zeilenparameter immer leer ist. Gibt es ein Szenario, in dem es gültige Daten haben wird? Ich werde die Dokumentation aktualisieren.

Eine andere Sache, die mir auffällt, ist, dass der an den Callback übergebene Zeilenparameter immer leer ist. Gibt es ein Szenario, in dem es gültige Daten haben wird? Ich werde die Dokumentation aktualisieren.

Es gibt die Option rowCollectionOnRequestCompletion , die beim Verbinden angegeben werden kann. Es als globale Option zu haben, ist etwas seltsam, ich denke, es wäre sinnvoller, dies auf Anfrage zu haben.

Die Hauptidee dahinter ist folgende: Wenn die Option rowCollectionOnRequestCompletion aktiviert ist, werden Zeilen im Speicher gespeichert, bis das entsprechende Request abgeschlossen ist (und derzeit glaube ich, dass sie sogar bis zum Request wird gc'ed). Bei kleinen Resultsets ist das normalerweise kein Problem, aber bei großen Resultsets kann sich das stark negativ auf die Leistung auswirken . Der bevorzugte Ansatz besteht darin, einfach den Callback rows zu verwenden, und wenn Sie alle Zeilen in einem Array sammeln müssen, können Sie dies selbst tun.

Ich denke nicht, dass dies optimal ist, es wäre wahrscheinlich besser, diese konfigurierbare pro Anfrage statt pro Verbindung zu haben - aber der Konstruktor Request akzeptiert noch keine Optionen, also müsste die Methodensignatur sein geändert.

Gibt es einen Grund für das Ereignis „requestCompleted“?

Ich bin mir nicht sicher - dies wurde hinzugefügt, bevor ich anfing, an langweilig zu arbeiten.

#459 geöffnet, um das rowCollectionOnRequestCompletion-Problem zu verfolgen. Ich bin sicher, wir können das hinzufügen und handhaben
auf abwärtskompatible Weise.

Durch das Festlegen der Callback-Eigenschaft wird der übergebene Callback überschrieben
an den Request-Konstruktor. Stattdessen sollten Sie den Rückruf von anrufen
async innerhalb des Callbacks, der an den Request-Konstruktor übergeben wird. Auf diese Weise Sie
kann auch Fehler korrekt an async weiterleiten.

@arthurschreiber Du hast Recht. Das ist der richtige Weg, um die Ergebnisse/Fehler nicht zu überschreiben. Klappt wunderbar :-)

#464 erstellt, um die Dokumentation zu Event: 'requestCompleted' zu verbessern

Für Leute, die von Google hierher kommen (so wie ich), bietet der mühsame Verbindungspool eine großartige Möglichkeit, dieses Problem zu bekämpfen. Die Verwendung pool.acquire((err, connection) => ...) und connection.release() ist eine einfache Möglichkeit, Parallelitätsprobleme zu vermeiden. Wenn Sie nicht mehrere Verbindungen wünschen, konfigurieren Sie den Pool mit {min: 1, max: 1} , um Abfragen effektiv zu serialisieren.

Bitte schlagen Sie vor, dass der Windows-Authentifizierungsmodus für die nodejs2Sql-Konnektivität nicht funktioniert.

@wafaabbass tedious unterstützt noch keine integrierte Windows-Authentifizierung. Wir arbeiten daran, Sie können es in #415 verfolgen.

Hey zusammen, ich habe das gleiche Problem.

debuggen:

Anfragen können nur im LoggedIn-Zustand gestellt werden, nicht im SentClientRequest-Zustand
(node:5180) UnhandledPromiseRejectionWarning: Unhandled Promise Rejection (Rejection ID: 1): RequestError: Anfragen können nur im LoggedIn-Zustand gestellt werden, nicht im SentClientRequest-Zustand
(node:5180) [DEP0018] DeprecationWarning: Unbehandelte Zurückweisungen von Promises sind veraltet. Zukünftig werden nicht verarbeitete Promise-Ablehnungen den Node.js-Prozess mit einem Exit-Code ungleich Null beenden.

Ist es schon gelöst? Das ist mein 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()

}

Wenn ich willIGetArea und willIGetSymptom aufrufe, funktioniert das alles, aber wenn ich es mit willGetModifier aufrufe, habe ich das gleiche Problem. Vielen Dank.

Falls es jemand hilfreich findet, da Google diese Seite zuerst für diese Fehlermeldung zurückgibt.
Für meinen auf einer einzigen Verbindung basierenden Dienst funktionierte es, einen Promise-basierten Wrapper für die Verbindung zu erstellen. Ich bin mir jedoch nicht sicher, ob es im Allgemeinen eine gute Lösung ist, da ich kein erfahrener js-Entwickler bin:

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

Und die Abfrage selbst kann wie folgt ausgeführt werden:

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

Damit kann getObjList nun jederzeit ohne Überprüfung des Verbindungsstatus ausgeführt werden.

Sorge hier ist gut. In meinem Code mache ich eine Verbindungsanfrage, eine SQL-Anfrage und dann eine Einfügeanfrage. Es schlägt bei der Einfügeanforderung fehl, es sei denn, ich mache eine weitere Verbindungsanforderung zwischen SQL und der Einfügeanforderung, selbst nachdem ich nach der SQL-Anforderung auf requestCompleted gewartet habe.

Eine häufige Ursache für diesen Fehler ist, dass _auf einer Verbindung nur jeweils eine Abfrage ausgeführt werden kann_. Sie müssen warten, bis der Anforderungsrückruf ausgeführt wird, entweder mit einem Fehler oder mit dem Ergebnis, bevor Sie eine weitere Anforderung stellen. Bitte besuchen Sie die FAQ-Seite hier

Da scheint es zu viele verschiedene Probleme in diesem Forum zu geben. Der Fehler Requests can only be made in the LoggedIn state, not the SentClientRequest state kann verschiedene Ursachen haben. Wenn dieser Fehler auftritt und Sie die FAQ-Seite nicht hilfreich fanden, melden Sie bitte Ihr eigenes Problem mit den folgenden Informationen, damit wir Ihr Problem genauer angehen können:

  • Langweilige Version
  • Ihre Konfiguration für Connection
  • Ein reproduzierbares Skript

Danke! 😄

Konnte dies lösen, Problem war auf meiner Seite, wusste nicht, dass Sie für jede Anfrage eine separate Verbindung benötigen

@mgarf Was war deine Lösung? Mir war auch nicht klar, dass ich für jede Anfrage eine separate Verbindung benötige. Ich habe Parallelitätsprobleme wie die anderen in dieser Diskussion.

@mikebutak Sie benötigen nicht unbedingt für jede Anfrage eine separate Verbindung. Sie brauchen nur eine Verbindung, die jeweils eine Anfrage bearbeitet. Um mehrere Anfragen auf einer Verbindung zu verarbeiten, müssen Sie eine neue Anfrage im Rückruf der vorherigen ausführen, nachdem sie abgeschlossen ist. Zum Beispiel,

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

Ich poste eine vollständige Lösung mit Rückrufen für dieses Problem. Ich hatte das gleiche Problem und bin bei Google hier gelandet. Ich habe diesen Code mit einem Skript getestet, das jede Sekunde 1 HTTP-Aufruf und auch eine halbe Sekunde ohne Probleme ausführt.

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

Ich poste eine vollständige Lösung mit Rückrufen für dieses Problem. Ich hatte das gleiche Problem und bin bei Google hier gelandet. Ich habe diesen Code mit einem Skript getestet, das jede Sekunde 1 HTTP-Aufruf und auch eine halbe Sekunde ohne Probleme ausführt.

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

Diese Lösung funktioniert, aber ist sie eine gültige Implementierung? Wie machbar ist das, wenn man bedenkt, dass es möglicherweise Hunderttausende von Inserts/Selects gibt und eine Verbindung für jeden von ihnen erstellt wird? Danke für die Lösung übrigens, ich versuche nur zu verstehen, ob es eine praktikable Lösung ist. Vor allem, wenn wir eine Azure-Funktion verwenden?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen