Tedious: Las solicitudes solo se pueden realizar en el estado LoggedIn, no en el estado SentClientRequest (código: 'EINVALIDSTATE')

Creado en 28 sept. 2016  ·  23Comentarios  ·  Fuente: tediousjs/tedious

En mi ejemplo a continuación, estoy tratando de ejecutar en serie una serie de funciones. Pero las solicitudes solo se pueden realizar en el estado de sesión iniciada, por lo que tuve que verificar ese connection.state !== connection.STATE.LOGGED_IN . Y como puede ver en la función Leer a continuación, tuvo que volver a poner la solicitud en la cola para deshacerse del error.

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

¿Hay una mejor manera de lograr esto? Veo que se plantearon problemas similares antes de los números 19 y 355 . ¿Hay alguna forma recomendada de ejecutar varias solicitudes en una conexión?

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

Comentario más útil

Para las personas que vienen aquí desde Google (como lo hice yo): tedious-connection-pool ofrece una excelente manera de combatir este problema. El uso pool.acquire((err, connection) => ...) y connection.release() es una forma fácil de evitar problemas de simultaneidad. Si no desea conexiones múltiples, configure el grupo con {min: 1, max: 1} para serializar consultas de manera efectiva.

Todos 23 comentarios

Hay una propiedad de 'devolución de llamada' que puede configurar en Solicitud y hay un evento 'solicitud Completada' en Solicitud, ambos parecen funcionar. Y esas son las dos cosas que no probamos :)

Es posible que se necesite algo de limpieza y documentación al respecto.

Pruebe estos y vea si funciona.

Usando 'devolución de llamada':

  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 'solicitud Completada':

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

De hecho, pondré estas notas en https://github.com/tediousjs/tedious/issues/61 y cerraré esta porque es el mismo problema.

Reapertura del tema. https://github.com/tediousjs/tedious/issues/61 no tiene ninguna relación. Los problemas a los que se refiere @SaloniSonpal están realmente cerrados. Así que reabrir esto para el seguimiento.

Esto necesita algo de limpieza y documentación.

Gracias @tvrprasad. Ambos funcionaron y son mucho más limpios. El enfoque callback ofrece la ventaja de manejar el error. Event: 'requestCompleted' definitivamente necesita ser documentado.

(Enviando desde mi teléfono, disculpe la brevedad).

Establecer la propiedad de devolución de llamada sobrescribirá la devolución de llamada que pasó
al constructor de solicitudes. En su lugar, debe llamar a la devolución de llamada desde
async dentro de la devolución de llamada pasada al constructor de The Request. De esta manera tu
también puede reenviar errores correctamente a async.

Ah, esa devolución de llamada perdida está configurada en la devolución de llamada pasada. Entonces, la forma correcta sería:

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

@arthurschreiber ¿ Algún motivo para el evento 'requestCompleted'? Parece redundante. Otra cosa que noto es que el parámetro de filas pasado a la devolución de llamada siempre está vacío. ¿Hay un escenario en el que tendrá datos válidos? Actualizaré la documentación.

Otra cosa que noto es que el parámetro de filas pasado a la devolución de llamada siempre está vacío. ¿Hay un escenario en el que tendrá datos válidos? Actualizaré la documentación.

Existe la opción rowCollectionOnRequestCompletion que se puede especificar al conectarse. Tenerlo como una opción global es un poco raro, creo que tendría más sentido tener esto por solicitud.

La idea principal detrás de esto es la siguiente: si la opción rowCollectionOnRequestCompletion está habilitada, las filas se almacenan en la memoria hasta que se complete el Request correspondiente (y actualmente, creo que incluso se mantienen hasta el Request obtiene GC). Con conjuntos de resultados pequeños, eso no suele ser un problema, pero con conjuntos de resultados grandes, eso puede tener un gran impacto negativo en el rendimiento. El enfoque preferido es simplemente usar la devolución de llamada rows , y si necesita recopilar todas las filas en una matriz, puede hacerlo usted mismo.

No creo que esto sea óptimo, tener esto configurable por solicitud en lugar de por conexión probablemente sería mejor, pero el constructor Request aún no toma ninguna opción, por lo que la firma del método tendría que ser cambió.

¿Algún motivo para el evento 'requestCompleted'?

No estoy seguro, esto se agregó antes de que comenzara a trabajar en tedioso.

Abrió #459 para rastrear el problema de rowCollectionOnRequestCompletion. Estoy seguro de que podemos agregar y manejar eso
de una manera compatible con versiones anteriores.

Establecer la propiedad de devolución de llamada sobrescribirá la devolución de llamada que pasó
al constructor de solicitudes. En su lugar, debe llamar a la devolución de llamada desde
async dentro de la devolución de llamada pasada al constructor de The Request. De esta manera tu
también puede reenviar errores correctamente a async.

@arthurschreiber Tienes razón. Esa es la forma correcta de no anular los resultados/errores. Funciona de maravilla :-)

Creado #464 para mejorar la documentación en Event: 'requestCompleted'

Para las personas que vienen aquí desde Google (como lo hice yo): tedious-connection-pool ofrece una excelente manera de combatir este problema. El uso pool.acquire((err, connection) => ...) y connection.release() es una forma fácil de evitar problemas de simultaneidad. Si no desea conexiones múltiples, configure el grupo con {min: 1, max: 1} para serializar consultas de manera efectiva.

Alguien, por favor, sugiera que el modo de autenticación de Windows no funciona para la conectividad nodejs2Sql.

@wafaabbass tedious aún no es compatible con la autenticación integrada de Windows. Estamos trabajando en ello, puedes seguirlo en el #415.

Hola a todos, tengo el mismo problema.

depurar:

Las solicitudes solo se pueden realizar en el estado LoggedIn, no en el estado SentClientRequest
(nodo: 5180) UnhandledPromiseRejectionWarning: rechazo de promesa no manejado (id de rechazo: 1): RequestError: las solicitudes solo se pueden realizar en el estado LoggedIn, no en el estado SentClientRequest
(nodo: 5180) [DEP0018] DeprecationWarning: los rechazos de promesa no controlados están en desuso. En el futuro, los rechazos de promesas que no se controlen terminarán el proceso de Node.js con un código de salida distinto de cero.

¿Ya está resuelto? Este es mi 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()

}

Si llamo a willIGetArea y willIGetSymptom, todo esto funciona, pero si lo llamo con willGetModifier, también tengo el mismo problema. Muchas gracias.

En caso de que alguien lo encuentre útil, ya que Google devuelve esta página primero para este mensaje de error.
Funcionó para mi servicio basado en una sola conexión para hacer un contenedor basado en Promise para la conexión. Sin embargo, no estoy seguro de que sea una buena solución en general, ya que no soy un desarrollador js experimentado:

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

Y la consulta en sí se puede ejecutar así:

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

Entonces, getObjList ahora se puede ejecutar en cualquier momento sin verificar el estado de la conexión.

La preocupación aquí está bien. En mi código hago una solicitud de conexión, una solicitud de sql y luego una solicitud de inserción. Se produce un error en la solicitud de inserción a menos que haga otra solicitud de conexión entre sql y la solicitud de inserción incluso después de esperar requestCompleted después de la solicitud de sql.

Una razón común para este error es que _solo se puede ejecutar una consulta en una conexión a la vez_. Debe esperar hasta que se ejecute la devolución de llamada de solicitud, ya sea con un error o con el resultado antes de realizar otra solicitud. Visite la página de preguntas frecuentes aquí

Dado que parece haber demasiados problemas en este foro. El error Requests can only be made in the LoggedIn state, not the SentClientRequest state puede ser causado por una variedad de razones diferentes. Si experimenta este error y no encontró útil la página de preguntas frecuentes, plantee su propio problema con la siguiente información para que podamos abordar su problema con más precisión:

  • Versión tediosa
  • Su configuración para Conexión
  • Un guión reproducible

¡Gracias! 😄

Pude resolver esto, el problema estaba de mi lado, no me di cuenta de que necesita una conexión separada para cada solicitud

@mgarf ¿Cuál fue su solución? Tampoco me di cuenta de que necesito una conexión separada para cada solicitud. Tengo problemas de concurrencia como los demás en esta discusión.

@mikebutak , no necesariamente necesita una conexión separada para cada solicitud. Solo necesita una conexión que maneje una solicitud _a la vez_. Para manejar varias solicitudes en una conexión, debe ejecutar una nueva solicitud en la devolución de llamada de la anterior, una vez que finalice. Por ejemplo,

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

Estoy publicando una solución completa usando devoluciones de llamada para este problema. Tuve el mismo problema y cuando busqué en Google terminé aquí. Probé este código con un script que ejecuta 1 llamada http cada segundo y también medio segundo sin 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);
});

Estoy publicando una solución completa usando devoluciones de llamada para este problema. Tuve el mismo problema y cuando busqué en Google terminé aquí. Probé este código con un script que ejecuta 1 llamada http cada segundo y también medio segundo sin 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 solución funciona, pero ¿es una implementación válida? ¿Qué tan factible es eso, considerando que puede haber cientos de miles de inserciones/selecciones y creando una conexión para cada una de ellas? Gracias por la solución por cierto, solo estoy tratando de entender si es una solución viable. ¿Especialmente si estamos usando una función de Azure?

¿Fue útil esta página
0 / 5 - 0 calificaciones